JavaScript 垃圾回收及內(nèi)存泄漏

一、內(nèi)存

1、內(nèi)存概述

在硬件層面但绕,計(jì)算機(jī)內(nèi)存是由大量觸發(fā)電路組成的。每個(gè)觸發(fā)電路包含了一些晶體管并且能夠存儲(chǔ)一個(gè)位(bit)惶看。
單個(gè)觸發(fā)器可通過唯一的標(biāo)識(shí)符尋址捏顺,因此我們可以讀取并重寫它們。
在概念上碳竟,我們可以將整個(gè)計(jì)算機(jī)內(nèi)存看作是我們可以讀和寫的一個(gè)由位組成的大數(shù)組草丧。

很多東西都存儲(chǔ)在內(nèi)存中:
1、所有程序使用的所有變量和數(shù)據(jù)莹桅;
2昌执、程序的代碼,包括操作系統(tǒng)的诈泼。

靜態(tài)和動(dòng)態(tài)內(nèi)存分配:

2懂拾、內(nèi)存生命周期

每種編程語(yǔ)言的內(nèi)存生命周期都是差不多的:分配內(nèi)存 => 使用內(nèi)存 => 釋放內(nèi)存

“使用內(nèi)存”在所有語(yǔ)言中都是顯性的,而“分配內(nèi)存”和“釋放內(nèi)存”在低級(jí)語(yǔ)言中是明確的铐达,在 JavaScript 等高級(jí)語(yǔ)言中大多是隱性的岖赋。

二、JavaScript 內(nèi)存分配概述

1瓮孙、概述

像 C 語(yǔ)言唐断,擁有底層原始的內(nèi)存管理方法,如 malloc() 杭抠、free()脸甘。
而 JavaScript,在創(chuàng)建(對(duì)象偏灿、字符串等)時(shí)分配內(nèi)存丹诀,并且在不再使用時(shí)自動(dòng)釋放。后面的過程被稱為垃圾回收。
這種自動(dòng)的垃圾回收就是管理混亂的根源铆遭,并造成了 JavaScript (其他高級(jí)語(yǔ)言)開發(fā)者不關(guān)心內(nèi)存管理的錯(cuò)誤現(xiàn)象硝桩。
即使是高級(jí)語(yǔ)言,自動(dòng)內(nèi)存管理也會(huì)遇到問題——垃圾回收中的錯(cuò)誤或性能等問題枚荣,因此碗脊,我們需要對(duì)內(nèi)存管理有一定的了解,以便處理內(nèi)存管理中遇到的問題棍弄。

2望薄、JavaScript 的回收機(jī)制

JavaScript垃圾回收的機(jī)制很簡(jiǎn)單:找出不再使用的變量疟游,然后釋放掉其占用的內(nèi)存呼畸,但是這個(gè)過程不是實(shí)時(shí)的,因?yàn)槠溟_銷比較大颁虐,所以垃圾回收系統(tǒng)(GC)會(huì)按照固定的時(shí)間間隔,周期性的執(zhí)行蛮原。

由于事實(shí)上發(fā)現(xiàn)內(nèi)存“不再被需要”是不可判定的,因此垃圾收集的通常解決方案都存在局限性另绩。

用于標(biāo)記的無(wú)用變量的策略可能因?qū)崿F(xiàn)而有所區(qū)別儒陨,通常情況下有兩種實(shí)現(xiàn)方式:標(biāo)記清除和引用計(jì)數(shù)。

1)引用計(jì)數(shù)

這是最簡(jiǎn)單的垃圾回收算法笋籽。如果一個(gè)對(duì)象指向它的引用數(shù)為 0蹦漠,那么它就應(yīng)該被“垃圾回收”了。

var o1 = {
  o2: {
    x: 1
  }
};
// 2 objects are created. // 'o2' is referenced by 'o1' object as one of its properties.// None can be garbage-collected

var o3 = o1; // the 'o3' variable is the second thing that
            // has a reference to the object pointed by 'o1'.
                                                       
o1 = 1;      // now, the object that was originally in 'o1' has a         
            // single reference, embodied by the 'o3' variable

var o4 = o3.o2; // reference to 'o2' property of the object.
                // This object has now 2 references: one as
                // a property.
                // The other as the 'o4' variable

o3 = '374'; // The object that was originally in 'o1' has now zero
            // references to it.
            // It can be garbage-collected.
            // However, what was its 'o2' property is still
            // referenced by the 'o4' variable, so it cannot be
            // freed.

o4 = null; // what was the 'o2' property of the object originally in
           // 'o1' has zero references to it.
           // It can be garbage collected.

但出現(xiàn)循環(huán)依賴就會(huì)產(chǎn)生限制车海。

function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}

f();

上面代碼中笛园,兩個(gè)對(duì)象彼此引用,創(chuàng)建了一個(gè)循環(huán)侍芝。
在函數(shù)調(diào)用之后研铆,它們離開了作用域,因此它們實(shí)際上已經(jīng)無(wú)用了州叠,可以被釋放了棵红。然而,引用計(jì)數(shù)算法認(rèn)為咧栗,由于兩個(gè)對(duì)象中的每一個(gè)至少被引用了一次逆甜,所以也不能被垃圾回收。

2)標(biāo)記掃描算法

為了確定一個(gè)對(duì)象是否被需要致板,這個(gè)算法會(huì)確定對(duì)象是否可以訪問交煞。
該算法由以下步驟組成:

垃圾回收器構(gòu)建“roots”列表。Roots 通常是代碼中保留引用的全局變量可岂。在 JavaScript 中错敢,“window” 對(duì)象可以作為 root 全局變量示例。
所有的 roots 被檢查并標(biāo)記為 active(即不是垃圾)。所有的 children 也被遞歸檢查稚茅。從 root 能夠到達(dá)的一切都不被認(rèn)為是垃圾纸淮。
所有為被標(biāo)記為 active 的內(nèi)存可以被認(rèn)為是垃圾了。收集器限制可以釋放這些內(nèi)存并將其返回到操作系統(tǒng)亚享。

function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}

f();

即使在循環(huán)情況下咽块,在函數(shù)調(diào)用后,兩個(gè)對(duì)象不再被從全局對(duì)象可訪問的東西所引用欺税。因此侈沪,垃圾回收器將發(fā)現(xiàn)它們是不可達(dá)的。

3晚凿、常見的 JavaScript 內(nèi)存泄漏

內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無(wú)法釋放亭罪,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果歼秽。

1)全局變量

JavaScript 正常模式下应役,對(duì)未聲明的變量的引用在全局對(duì)象內(nèi)創(chuàng)建一個(gè)新變量。即:

function foo(arg) {
bar = "some text";
}

等價(jià)于

function foo(arg) {
window.bar = "some text";
}

如果 bar 本應(yīng)是函數(shù) foo 內(nèi)的局部變量燥筷,而忘記聲明箩祥,就會(huì)意外地創(chuàng)建一個(gè)全局變量。這就產(chǎn)生了內(nèi)存泄漏肆氓。

下面代碼也會(huì)創(chuàng)建意外的全局變量:

function foo() {
this.var1 = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)// rather than being undefined.
foo();

為防止上述兩種創(chuàng)建意外全局變量的可能性袍祖,可以使用嚴(yán)格模式,在 JavaScript 代碼開頭添加“use strict”谢揪。嚴(yán)格模式中在上面兩種情況下會(huì)拋出錯(cuò)誤蕉陋。

由于全局無(wú)法自動(dòng)回收,除非將其賦值為 null 或重新進(jìn)行分配键耕。
那么在我們使用全局變量時(shí)要注意寺滚,尤其是用來臨時(shí)存儲(chǔ)和處理大量信息的全局變量非常值得關(guān)注。
如果你必須使用全局變量來存儲(chǔ)大量數(shù)據(jù)屈雄,請(qǐng)確保在使用完后村视,對(duì)其賦值為 null 或重新分配。

2)閉包

閉包可以維持函數(shù)內(nèi)局部變量酒奶,使其得不到釋放蚁孔。 上例定義事件回調(diào)時(shí),由于是函數(shù)內(nèi)定義函數(shù)惋嚎,并且內(nèi)部函數(shù)--事件回調(diào)的引用外暴了杠氢,形成了閉包:

function bindEvent(){  
   var obj=document.createElement("XXX");  
   obj.onclick=function(){  
     //Even if it's a empty function  
   }  
 }

解決之道,將事件處理函數(shù)定義在外部另伍,解除閉包,或者在定義事件處理函數(shù)的外部函數(shù)中鼻百,刪除對(duì)dom的引用:

 //將事件處理函數(shù)定義在外部  
 function onclickHandler(){  
   //do something  
 }  
 function bindEvent(){  
   var obj=document.createElement("XXX");  
   obj.onclick=onclickHandler;  
 }
 //在定義事件處理函數(shù)的外部函數(shù)中绞旅,刪除對(duì)dom的引用  
 function bindEvent(){  
   var obj=document.createElement("XXX");  
   obj.onclick=function(){  
     //Even if it's a empty function  
   }  
   obj=null;  
 }
3) 被遺忘的定時(shí)器或回調(diào)
var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); //This will be executed every ~5 seconds.

如上,定時(shí)器可能會(huì)引用不再需要的節(jié)點(diǎn)或數(shù)據(jù)温艇,若 renderer 在將來被移除因悲,整個(gè)定時(shí)器模塊都不再被需要,但 interval handler 因?yàn)?interval 的存貨勺爱,所以無(wú)法被回收(要停止 interva晃琳,才能回收)。因此 serverData 可能存儲(chǔ)了大量數(shù)據(jù)琐鲁,不能被回收进鸠。
那么我們就應(yīng)在他們不再被需要的時(shí)候顯示地刪除它們(或讓相關(guān)對(duì)象變?yōu)椴豢蛇_(dá))慨灭。

var element = document.getElementById('launch-button');
var counter = 0;
function onClick(event) {
   counter++;
   element.innerHtml = 'text ' + counter;
}
element.addEventListener('click', onClick);
// Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers // that don't handle cycles well.

如今鳖擒,現(xiàn)代瀏覽器(包括 IE 和 Edge)都使用的是現(xiàn)代垃圾回收算法台腥,可以檢測(cè)這些循環(huán)依賴并正確的處理它們。換句話說蒜撮,讓一個(gè)節(jié)點(diǎn)不可達(dá)暴构,可以不必而在調(diào)用 removeEventListener跪呈。
框架和庫(kù)段磨,例如 jQuery ,在處理掉節(jié)點(diǎn)之前會(huì)刪除 listeners (使用它們特定的 API)耗绿。這些由庫(kù)的內(nèi)部進(jìn)了處理苹支,確保泄漏不會(huì)發(fā)生。即使是在有問題的瀏覽器下運(yùn)行误阻,如债蜜。。究反。寻定。IE6。

4精耐、怎樣避免內(nèi)存泄漏

1)減少不必要的全局變量狼速,或者生命周期較長(zhǎng)的對(duì)象,及時(shí)對(duì)無(wú)用的數(shù)據(jù)進(jìn)行垃圾回收卦停;
2)注意程序邏輯向胡,避免“死循環(huán)”之類的 ;
3)避免創(chuàng)建過多的對(duì)象 原則:不用了的東西要及時(shí)歸還惊完。

參考:
https://blog.csdn.net/michael8512/article/details/77888000
https://blog.csdn.net/tangxiaolang101/article/details/78113871
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末僵芹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子小槐,更是在濱河造成了極大的恐慌拇派,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異件豌,居然都是意外死亡桐腌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門苟径,熙熙樓的掌柜王于貴愁眉苦臉地迎上來案站,“玉大人,你說我怎么就攤上這事棘街◇⊙危” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵遭殉,是天一觀的道長(zhǎng)石挂。 經(jīng)常有香客問我,道長(zhǎng)险污,這世上最難降的妖魔是什么痹愚? 我笑而不...
    開封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮蛔糯,結(jié)果婚禮上拯腮,老公的妹妹穿的比我還像新娘。我一直安慰自己蚁飒,他們只是感情好动壤,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淮逻,像睡著了一般琼懊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爬早,一...
    開封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天哼丈,我揣著相機(jī)與錄音,去河邊找鬼筛严。 笑死醉旦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的脑漫。 我是一名探鬼主播髓抑,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼优幸!你這毒婦竟也來了吨拍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤网杆,失蹤者是張志新(化名)和其女友劉穎羹饰,沒想到半個(gè)月后伊滋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡队秩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年笑旺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馍资。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筒主,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸟蟹,到底是詐尸還是另有隱情乌妙,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布建钥,位于F島的核電站藤韵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏熊经。R本人自食惡果不足惜泽艘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镐依。 院中可真熱鬧匹涮,春花似錦、人聲如沸馋吗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宏粤。三九已至,卻和暖如春灼卢,著一層夾襖步出監(jiān)牢的瞬間绍哎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工鞋真, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崇堰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓涩咖,卻偏偏與公主長(zhǎng)得像海诲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子檩互,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

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