javascript垃圾回收與內(nèi)存泄漏

垃圾回收機制

自動垃圾收集機制

  • javascript具有自動垃圾收集機制猴贰,也就是說河狐,執(zhí)行環(huán)境會負責管理代碼執(zhí)行過程中使用的內(nèi)存瑟捣。即開發(fā)人員不需要當心內(nèi)存使用的問題栅干,所需內(nèi)存的分配以及無用內(nèi)存的回收完全實現(xiàn)了自動管理碱鳞。這種垃圾回收機制的原理:找出那些不再繼續(xù)使用的變量,然后釋放其內(nèi)存窿给。為此崩泡,垃圾收集器會按照固定的時間間隔,周期性地執(zhí)行這一操作呛伴。

局部變量的生命周期

  • 局部變量只在函數(shù)執(zhí)行的過程中存在谒所。在這個過程中,會為局部變量在棧內(nèi)存或堆內(nèi)存上分配相應(yīng)的空間褐隆,以便存儲他們的值剖踊。然后再函數(shù)中使用這些變量,直至函數(shù)執(zhí)行結(jié)束歇攻。此時梆造,局部變量就沒有存在的必要了,因此可以釋放他們的內(nèi)存以供將來使用屡穗。

javascript中的2中垃圾回收機制

標記清除(Mark-and-sweep)

  • 包括標記階段和清除階段忽肛。
  • 標記階段:垃圾回收器創(chuàng)建了一個roots列表屹逛。Roots通常是代碼中全局變量的引用汛骂。javascript中评腺,window對象是一個全局變量,被當作root图张。window對象總是存在诈悍,因此垃圾回收器可以檢查他和他所有子對象是否存在(即不是垃圾)。所有的roots被檢查和標記為激活(即不是垃圾)适袜。所有的子對象也被遞歸檢查舷夺,檢查從root開始的所有對象,如果是可達的疫萤,則打上標記敢伸。標記階段到此結(jié)束池颈。
  • 清除階段:標記階段完成時,被標記的對象被視為“存活”對象躯砰。然后重新進入遞歸掃描階段琢歇,將沒有標記的對象進行回收。這一階段稱為清除階段揭保。在掃描的同時涌矢,還需要將存活的對象的標記清除掉,以便下一次GC操作做準備塔次。

引用計數(shù)(reference counting)

  • 引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)名秀。當聲明了一個變量并將一個引用類型賦給該變量時匕得,則這個值的引用次數(shù)就是1。如果同一個值又被賦給另一個變量汁掠,則將該引用次數(shù)再加1考阱。相反,如果包含對這個值得引用變量又取得了另一個值乞榨,則這個值的引用次數(shù)就減1吃既。當這個值得引用次數(shù)變?yōu)?時,則說明沒有辦法在訪問這個值了河质,因而久可以將其占用的內(nèi)存空間回收回來震叙。這樣,垃圾回收器下次再運行時淫半,會釋放那些引用次數(shù)為0的值所占用的內(nèi)存匣砖。

內(nèi)存泄漏

  • 內(nèi)存泄漏是指應(yīng)用程序使用過的內(nèi)存片段猴鲫,在不需要的時候,不能返回到操作系統(tǒng)或可用的內(nèi)存池中的情況拂共。javascript中的內(nèi)存泄漏同理宜狐,當一個值不再需要被引用了蛇捌,但卻無法被垃圾回收器回收的情況咱台,就是內(nèi)存泄露回溺。

javascript中3種常見的內(nèi)存泄漏

1. 意外的全局變量

舉個例子(普通變量):

function fun(){
  globalVar = "this is a hidden global variable";
}
//在函數(shù)體內(nèi)部定義變量,如果沒有使用var聲明萍恕,則定義的變量是全局變量车要,即成為window的一個屬性
//而window總是存在,因此不會被回收

再舉個例子(由this創(chuàng)建的變量):

function foo() {
    this.variable = "potential accidental global";
    console.log(this)
}
// Foo 調(diào)用自己维哈,this 指向了全局對象(window)
// 而不是 undefined
foo();
image.png
  • 解決方案:在js文件頭部加上(ES5)use strict阔挠,使用嚴格模式脑蠕,可以避免此類錯誤的發(fā)生谴仙。如果必須使用全局變量存儲大量數(shù)據(jù)時,確保用完以后把它設(shè)置為 null 或者重新定義揩局。

2.被遺忘的計數(shù)器或者回調(diào)函數(shù)

舉個例子:

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 處理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
//重復(fù)計數(shù)器并沒有使用 clearInterval()終止掀虎,導致內(nèi)存無法回收
  • 對于觀察者的例子,一旦它們不再需要(或者關(guān)聯(lián)的對象變成不可達)驰怎,明確地移除它們非常重要二打。老的 IE 6 是無法處理循環(huán)引用的。如今症杏,即使沒有明確移除它們鸳慈,一旦觀察者對象變成不可達喧伞,大部分瀏覽器是可以回收觀察者處理函數(shù)的。

3. 脫離DOM的引用

  • 有時翁逞,保存 DOM 節(jié)點內(nèi)部數(shù)據(jù)結(jié)構(gòu)很有用溉仑。假如你想快速更新表格的幾行內(nèi)容浊竟,把每一行 DOM 存成字典(JSON 鍵值對)或者數(shù)組很有意義。此時必怜,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中后频,另一個在字典中卑惜。將來你決定刪除這些行時,需要把兩個引用都清除更米。
var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};
function doStuff() {
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
    // 更多邏輯
}
function removeButton() {
    // 按鈕是 body 的后代元素
    document.body.removeChild(document.getElementById('button'));
    // 此時毫痕,仍舊存在一個全局的 #button 的引用
    // elements 字典所引用的button 元素仍舊在內(nèi)存中,不能被 GC 回收眶痰。
}

4.閉包(正常情況下不會造成內(nèi)存泄漏)

由于IE9 之前的版本對JScript 對象和COM 對象使用不同的垃圾收集梯啤。因此閉包在IE 的這些版本中會導致一些特殊的問題。具體來說七婴,如果閉包的作用域鏈中保存著一個HTML 元素打厘,那么就意味著該元素將無法被銷毀

reference -- javascript高級程序設(shè)計第三版

舉個例子:

function closure(){
    var element = document.getElementById("someElement");
    element.onclick = function(){
        alert(element.id);
    };
}
  • 以上代碼創(chuàng)建了一個作為element 元素事件處理程序的閉包嵌施,而這個閉包則又創(chuàng)建了一個循環(huán)引用吗伤。由于匿名函數(shù)保存了一個對closure()的活動對象的引用硫眨,因此就會導致無法減少element 的引用數(shù)。只要匿名函數(shù)存在礁阁,element 的引用數(shù)至少也是1巧号,因此它所占用的內(nèi)存就永遠不會被回收。

解決方案:把element.id 的一個副本保存在一個變量中姥闭,從而消除閉包中該變量的循環(huán)引用同時將element變量設(shè)為null丹鸿。

function closure(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        alert(id);
    };
    element = null;
}
  • 因此可以看出,閉包并不會引起內(nèi)存泄漏泣栈,只是由于IE9之前的版本對JScript對象和COM對象使用不同的垃圾收集卜高,從而導致內(nèi)存無法進行回收,這是IE的問題南片,所以正常使用閉包的情況下不會導致內(nèi)存泄漏掺涛。

reference -- GC的三大基礎(chǔ)算法

reference -- javascript內(nèi)存泄露及其避免

閉包會造成內(nèi)存泄漏嗎?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末薪缆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伞广,更是在濱河造成了極大的恐慌拣帽,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嚼锄,死亡現(xiàn)場離奇詭異减拭,居然都是意外死亡,警方通過查閱死者的電腦和手機区丑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門拧粪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來修陡,“玉大人,你說我怎么就攤上這事可霎∑茄唬” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵癣朗,是天一觀的道長拾因。 經(jīng)常有香客問我,道長旷余,這世上最難降的妖魔是什么绢记? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮荣暮,結(jié)果婚禮上庭惜,老公的妹妹穿的比我還像新娘罩驻。我一直安慰自己穗酥,他們只是感情好,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布惠遏。 她就那樣靜靜地躺著砾跃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪节吮。 梳的紋絲不亂的頭發(fā)上抽高,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音透绩,去河邊找鬼翘骂。 笑死,一個胖子當著我的面吹牛帚豪,可吹牛的內(nèi)容都是我干的碳竟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼狸臣,長吁一口氣:“原來是場噩夢啊……” “哼莹桅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起烛亦,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤诈泼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后煤禽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铐达,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年檬果,在試婚紗的時候發(fā)現(xiàn)自己被綠了瓮孙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贾节。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖衷畦,靈堂內(nèi)的尸體忽然破棺而出栗涂,到底是詐尸還是另有隱情,我是刑警寧澤祈争,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布斤程,位于F島的核電站,受9級特大地震影響菩混,放射性物質(zhì)發(fā)生泄漏忿墅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一沮峡、第九天 我趴在偏房一處隱蔽的房頂上張望疚脐。 院中可真熱鬧,春花似錦邢疙、人聲如沸棍弄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呼畸。三九已至,卻和暖如春颁虐,著一層夾襖步出監(jiān)牢的瞬間蛮原,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工另绩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留儒陨,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓笋籽,卻偏偏與公主長得像蹦漠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子干签,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容