摘要:的內(nèi)存泄漏對于這門語言的使用者來說,大多數(shù)的使用者的內(nèi)存管理意識都不強(qiáng)。內(nèi)存泄漏的定義指由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。
javascript的內(nèi)存泄漏
對于JavaScript這門語言的使用者來說,大多數(shù)的使用者的內(nèi)存管理意識都不強(qiáng)。因?yàn)镴avaScript一直以來都只作為在網(wǎng)頁上使用的腳本語言,而網(wǎng)頁往往都不會(huì)長時(shí)間的運(yùn)行,所以使用者對JavaScript的運(yùn)行時(shí)長和內(nèi)存控制都比較漠視。但隨著Spa(單頁應(yīng)用)、node.js服務(wù)端程序和各種js工具的誕生,我們需要重新重視JavaScript的內(nèi)存管理。
內(nèi)存泄漏的定義JavaScript的內(nèi)存管理指由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。內(nèi)存泄漏并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)計(jì)錯(cuò)誤,失去了對該段內(nèi)存的控制,因而造成了內(nèi)存的浪費(fèi)。
首先JavaScript是一個(gè)有Garbage Collection 的語言,也就是我們不需要手動(dòng)的回收內(nèi)存。不同的JavaScript引擎有不同的垃圾回收機(jī)制,這里我們主要以V8這個(gè)被廣泛使用的JavaScript引擎為主。
JavaScript內(nèi)存分配和回收的關(guān)鍵詞:GC根、作用域GC根:一般指全局且不會(huì)被垃圾回收的對象,比如:window、document或者是頁面上存在的dom元素。JavaScript的垃圾回收算法會(huì)判斷某塊對象內(nèi)存是否是GC根可達(dá)(存在一條由GC根對象到該對象的引用),如果不是那這塊內(nèi)存將會(huì)被標(biāo)記回收。
作用域:在JavaScript的作用域里,我們能夠新建對象來分配內(nèi)存。比如說調(diào)用函數(shù),函數(shù)執(zhí)行的過程中就會(huì)創(chuàng)建一塊作用域,如果是創(chuàng)建的是作用域內(nèi)的局部對象,當(dāng)作用域運(yùn)行結(jié)束后,所有的局部對象(GC根無法觸及)都會(huì)被標(biāo)記回收,在JavaScript中能引起作用域分配的有函數(shù)調(diào)用、with和全局作用域。
作用域的分類:局部作用域、全局作用域、閉包作用域 局部作用域函數(shù)調(diào)用會(huì)創(chuàng)建局部作用域,在局部作用域中的新建的對象,如果函數(shù)運(yùn)行結(jié)束后,該對象沒有作用域外部的引用,那該對象將會(huì)標(biāo)記回收
全局作用域每個(gè)JavaScript進(jìn)程都會(huì)有一個(gè)全局作用域,全局作用域上的引用的對象都是常駐內(nèi)存的,直到進(jìn)程退出內(nèi)存才會(huì)自動(dòng)釋放。
手動(dòng)釋放全局作用域上的引用的對象有兩種方式:
global.foo = undefined
重新賦值改變引用
delete global.foo
閉包作用域刪除對象屬性
在JavaScript語言中有閉包的概念,閉包指的是包含自由變量的代碼塊、自由變量不是在這個(gè)代碼塊內(nèi)或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義(局部變量)。
var closure = (function(){ //這里是閉包的作用域 var i = 0 // i就是自由變量 return function(){ console.log(i++) } })()
閉包作用域會(huì)保持對自由變量的引用。上面代碼的引用鏈就是:
window -> closure -> i
閉包作用域還有一個(gè)重要的概念,閉包對象是當(dāng)前作用域中的所有內(nèi)部函數(shù)作用域共享的,并且這個(gè)當(dāng)前作用域的閉包對象中除了包含一條指向上一層作用域閉包對象的引用外,其余的存儲的變量引用一定是當(dāng)前作用域中的所有內(nèi)部函數(shù)作用域中使用到的變量
常見的幾種內(nèi)存泄漏的方式及使用chrome dev tools的排查方法 用全局變量緩存數(shù)據(jù)將全局變量作為緩存數(shù)據(jù)的一種方式,將之后要用到的數(shù)據(jù)都掛載到全局變量上,用完之后也不手動(dòng)釋放內(nèi)存(因?yàn)槿肿兞恳玫膶ο螅厥諜C(jī)制不會(huì)自動(dòng)回收),全局變量逐漸就積累了一些不用的對象,導(dǎo)致內(nèi)存泄漏
var x = []; function createSomeNodes() { var div; var i = 10000; var frag = document.createDocumentFragment(); for (; i > 0; i--) { div = document.createElement("div"); div.appendChild(document.createTextNode(i + " - " + new Date().toTimeString())); frag.appendChild(div); } document.getElementById("nodes").appendChild(frag); } function grow() { x.push(new Array(1000000).join("x")); createSomeNodes(); setTimeout(grow, 1000); } grow()
上面的代碼貼一張 timeline的截圖
主要看memory區(qū)域,通過分析代碼我們可以知道頁面上的dom節(jié)點(diǎn)是不斷增加的,所以memory里綠色的線(代表dom nodes)也是不斷升高的;而代表js heap的藍(lán)色的線是有升有降,當(dāng)整體趨勢是逐漸升高,這是因?yàn)閖s 有內(nèi)存回收機(jī)制,每當(dāng)內(nèi)存回收的時(shí)候藍(lán)色的線就會(huì)下降,但是存在部分內(nèi)存一直得不到釋放,所以藍(lán)色的線逐漸升高
var nodes = ""; (function () { var item = { name:new Array(1000000).join("x") } nodes = document.getElementById("nodes") nodes.item = item nodes.parentElement.removeChild(nodes) })()
這里的dom元素雖然已經(jīng)從頁面上移除了,但是js中仍然保存這對該dom元素的引用。
因?yàn)檫@段代碼是只執(zhí)行一次的,所以用timeline視圖會(huì)很難分析出來是否存在內(nèi)存泄漏,所以我們可以用 chrome dev tool 的 profile tab里的heap snapshot 工具來分析。
上面的代碼貼一張 heap snapshot 的summary模式的截圖
通過constructor的filter功能,我們把上面代碼中創(chuàng)建的長字符串找出來,可以看到代碼運(yùn)行結(jié)束后,內(nèi)存中的長字符串依然沒有被垃圾回收掉。
順帶提一下的是右邊紅框里的shadow size和 retainer size的含義
shadow size 指的是對象本地的大小
retainer size 指的是對象所引用內(nèi)存的大小,回收該對象是會(huì)將他引用的內(nèi)存也一并回收,所以retainer size 指代的是回收內(nèi)存后會(huì)釋放出來的內(nèi)存大小
上面我們可以看到 長字符串本身的shadow size和retainer size是一樣大的,這是引用長字符串沒有引用其他的對象,如果有引用其他對象,那shadow size 和retainer size將不一致。
閉包循環(huán)引用(function(){ var theThing = null var replaceThing = function () { var originalThing = theThing var unused = function () { if (originalThing) console.log("hi") } theThing = { longStr: new Array(1000000).join("*"), someMethod: function someMethod() { console.log("someMessage") } }; }; setInterval(replaceThing,100) })()
首先我們明確一下,unused是一個(gè)閉包,因?yàn)樗昧俗杂勺兞?originalThing,雖然它被沒有使用,但v8引擎并不會(huì)把它優(yōu)化掉,因?yàn)?JavaScript里存在eval函數(shù),所以v8引擎并不會(huì)隨便優(yōu)化掉暫時(shí)沒有使用的函數(shù)。
theThing 引用了someMethod,someMethod這個(gè)函數(shù)作用域隱式的和unused這個(gè)閉包共享一個(gè)閉包上下文。所以someMethod也引用了originalThing這個(gè)自由變量。
這里面的引用鏈?zhǔn)牵?/p>
GCHandler -> replaceThing -> theThing -> someMethod -> originalThing -> someMethod(old) -> originalThing(older)-> someMethod(older)
隨著setInterval的不斷執(zhí)行,這條引用鏈?zhǔn)遣粫?huì)斷的,所以內(nèi)存會(huì)不斷泄漏,直致程序崩潰。
因?yàn)槭情]包作用域引起的內(nèi)存泄漏,這時(shí)候最好的選擇是使用 chrome的heap snapshot的container視圖,我們通過container視圖能清楚的看到這條不斷泄漏內(nèi)存的引用鏈
由于作者水平有限,文中如有錯(cuò)誤還望指出,謝謝!
參考文檔:
百科內(nèi)存泄漏介紹
chrome devtolls
深入淺出nodejs
node-interview
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/82308.html
摘要:介紹瀏覽器的具有自動(dòng)垃圾回收機(jī)制,也就是說,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存。中的內(nèi)存泄漏問題程序的內(nèi)存溢出后,會(huì)使某一段函數(shù)體永遠(yuǎn)失效取決于當(dāng)時(shí)的代碼運(yùn)行到哪一個(gè)函數(shù),通常表現(xiàn)為程序突然卡死或程序出現(xiàn)異常。 showImg(https://segmentfault.com/img/remote/1460000018932880?w=4400&h=3080); 1. 介紹 瀏...
摘要:引用計(jì)數(shù)另一種不太常見的垃圾回收策略是引用計(jì)數(shù)。引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值被引用的次數(shù)。在采用引用計(jì)數(shù)的策略中,由于函數(shù)執(zhí)行之后,這兩個(gè)對象都離開了作用域,函數(shù)執(zhí)行完成之后,和還將會(huì)繼續(xù)存在,因?yàn)樗麄兊囊么螖?shù)永遠(yuǎn)不會(huì)是。 垃圾回收的必要性 由于字符串、對象和數(shù)組沒有固定大小,所以當(dāng)他們的大小已知時(shí),才能對他們進(jìn)行動(dòng)態(tài)的存儲分配。JavaScript程序每次創(chuàng)建字符串、數(shù)組或?qū)ο髸r(shí)...
摘要:積少成多,最后造成內(nèi)存泄漏。前端內(nèi)存泄漏的影響,都是發(fā)生在客戶機(jī)器上,而且基本上現(xiàn)代瀏覽器也會(huì)做好保護(hù)機(jī)制,一般自行刷新之后都會(huì)解決。但是,一旦后端繪制內(nèi)存泄漏造成宕機(jī)之后,整個(gè)服務(wù)器都會(huì)受影響,危險(xiǎn)性更大,搞不好年終獎(jiǎng)就沒了。 引言 Memory Leak 是最難排查調(diào)試的 Bug 種類之一,因?yàn)閮?nèi)存泄漏是個(gè) undecidable problem,只有開發(fā)者才能明確一塊內(nèi)存是不是需...
摘要:在前端項(xiàng)目端中,內(nèi)存泄露的定位往往比修復(fù)更加困難,即使瀏覽器有提供工具,但是面對成千上萬的元素和錯(cuò)綜復(fù)雜的引用關(guān)系,開發(fā)則依然很難快速定位到問題代碼塊。 在前端項(xiàng)目(PC端)中,內(nèi)存泄露的定位往往比修復(fù)更加困難,即使google瀏覽器有提供Memory工具,但是面對成千上萬的元素和錯(cuò)綜復(fù)雜的引用關(guān)系,開發(fā)則依然很難快速定位到問題代碼塊。 一、什么是內(nèi)存泄漏?系統(tǒng)進(jìn)程不再用到的內(nèi)存,沒有...
閱讀 1225·2021-09-26 09:55
閱讀 3191·2019-08-30 15:55
閱讀 965·2019-08-30 15:53
閱讀 2296·2019-08-30 13:59
閱讀 2380·2019-08-29 13:08
閱讀 1107·2019-08-29 12:19
閱讀 3302·2019-08-26 13:41
閱讀 418·2019-08-26 13:24