JavaScript垃圾回收機(jī)制

什么是垃圾回收機(jī)制?

和java,c#一樣,javascript也有垃圾回收的機(jī)制,比如說(shuō)c++和c就沒(méi)有垃圾回收機(jī)制。可能有這么一種傾向,垃圾回收機(jī)制必須有一種平臺(tái)來(lái)進(jìn)行回收。比如說(shuō)下面將講的javascript的執(zhí)行環(huán)境V8就會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中的垃圾回收。

javascript具有自動(dòng)垃圾回收機(jī)制,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存。原理就是找出那些不再繼續(xù)使用的變量,然后釋放其占有內(nèi)存。這整個(gè)過(guò)程也會(huì)按照一個(gè)固定的事件周期性的整形(時(shí)時(shí)的話(huà)開(kāi)銷(xiāo)太大)。

垃圾回收機(jī)制回收的是什么?

剛剛原理中提到要找出不再使用的變量,什么是不再使用的對(duì)象呢?不再使用的變量也就是生命周期結(jié)束的變量。目前javascript有兩種變量,全局變量和在函數(shù)中產(chǎn)生的局部變量(暫不考慮ES6中的塊級(jí)作用域)。

全局變量的聲明周期一直持續(xù)到瀏覽器關(guān)閉頁(yè)面才會(huì)清除,而局部變量只是在函數(shù)執(zhí)行時(shí)存在,而在這個(gè)過(guò)程中會(huì)為局部變量在棧或者堆上分配相應(yīng)的空間,來(lái)存儲(chǔ)他們的值,然后當(dāng)函數(shù)要使用這些變量的值時(shí)再取出來(lái)使用。一直到函數(shù)結(jié)束(閉包會(huì)不同)。
一旦函數(shù)結(jié)束,局部變量就不需要了,這時(shí)候就可以釋放他們的內(nèi)存。

var global = "I'm global";
function test(){
    var local = “I m local” ;
}
test();

這個(gè)例子里面,global在關(guān)閉瀏覽器時(shí)釋放,local在函數(shù)test結(jié)束后,釋放。

什么時(shí)候觸發(fā)垃圾回收

垃圾回收器周期性運(yùn)行,如果分配的內(nèi)存非常多,那么回收工作也會(huì)很艱巨,確定垃圾回收時(shí)間間隔就變成了一個(gè)值得思考的問(wèn)題。IE6的垃圾回收是根據(jù)內(nèi)存分配量運(yùn)行的,當(dāng)環(huán)境中存在256個(gè)變量,4096個(gè)對(duì)象,64k的字符串任意一種情況的時(shí)候就會(huì)觸發(fā)垃圾回收器工作,看起來(lái)很科學(xué),不用按一段時(shí)間就調(diào)用一次,但是如果環(huán)境中就是有這么多變量等一直存在,現(xiàn)在腳本如此復(fù)雜,很正常,那么結(jié)果就是垃圾回收器一直在工作,這樣瀏覽器就沒(méi)法兒玩兒了。

微軟在IE7中做了調(diào)整,觸發(fā)條件不再是固定的,而是動(dòng)態(tài)修改的,初始值和IE6相同,如果垃圾回收器回收的內(nèi)存分配量低于程序占用內(nèi)存的15%,說(shuō)明大部分內(nèi)存不可被回收,設(shè)的垃圾回收觸發(fā)條件過(guò)于敏感,這時(shí)候把臨街條件翻倍,如果回收的內(nèi)存高于85%,說(shuō)明大部分內(nèi)存早就該清理了,這時(shí)候把觸發(fā)條件置回。這樣就使垃圾回收工作智能了很多。

js的兩種回收機(jī)制

標(biāo)記清除(mark and sweep)

從語(yǔ)義上理解就比較好理解了,就是當(dāng)變量進(jìn)入到某個(gè)環(huán)境中的時(shí)候就把這個(gè)變量標(biāo)記一下,比如標(biāo)記為“進(jìn)入環(huán)境”,當(dāng)離開(kāi)的時(shí)候就把這個(gè)變量的標(biāo)記給清除掉,比如是“離開(kāi)環(huán)境”,而在這后面還有標(biāo)記的變量將被視為準(zhǔn)備刪除的變量。

垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記(可以使用任何標(biāo)記方式)。然后,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記。而在此之后再被加上的標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無(wú)法訪(fǎng)問(wèn)到這些變量了。最后,垃圾收集器完成內(nèi)存清除工作。銷(xiāo)毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。

這是javascript最常見(jiàn)的垃圾回收方式。至于上面有說(shuō)到的標(biāo)記,到底該如何標(biāo)記?好像是有很多方法,比如特殊位翻轉(zhuǎn),維護(hù)一個(gè)列表什么的。

工作原理:是當(dāng)變量進(jìn)入環(huán)境時(shí),將這個(gè)變量標(biāo)記為“進(jìn)入環(huán)境”。當(dāng)變量離開(kāi)環(huán)境時(shí),則將其標(biāo)記為“離開(kāi)環(huán)境”。標(biāo)記“離開(kāi)環(huán)境”的就回收內(nèi)存。
工作流程:

  1. 垃圾回收器,在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記。
  2. 去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記。
  3. 再被加上標(biāo)記的會(huì)被視為準(zhǔn)備刪除的變量。
  4. 垃圾回收器完成內(nèi)存清除工作,銷(xiāo)毀那些帶標(biāo)記的值并回收他們所占用的內(nèi)存空間。
引用計(jì)數(shù)(reference counting)

引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值被引用的次數(shù),當(dāng)聲明一個(gè)變量并將一個(gè)引用類(lèi)型的值賦給該變量時(shí),這個(gè)時(shí)候的引用類(lèi)型的值就會(huì)是引用次數(shù)+1了。如果同一個(gè)值又被賦給另外一個(gè)變量,則該值的引用次數(shù)又+1。

相反如果包含這個(gè)值的引用的變量又取得另外一個(gè)值,即被重新賦了值,那么這個(gè)值的引用就減一。當(dāng)這個(gè)值的引用次數(shù)變成0時(shí),表示沒(méi)有用到這個(gè)值,這個(gè)值也無(wú)法訪(fǎng)問(wèn),因此環(huán)境就會(huì)回收這個(gè)值所占用的內(nèi)存空間。這樣,當(dāng)垃圾收集器下次再運(yùn)行時(shí),它就會(huì)釋放引用次數(shù)為0的值所占用的內(nèi)存。

但是剛剛也說(shuō)了,第一種標(biāo)記清除是最經(jīng)常用到的,那么這個(gè)看起來(lái)這么好的引用計(jì)數(shù)為啥不被別人用了呢?
工作原理:跟蹤記錄每個(gè)值被引用的次數(shù)。
工作流程:

  1. 聲明了一個(gè)變量并將一個(gè)引用類(lèi)型的值賦值給這個(gè)變量,這個(gè)引用類(lèi)型值的引用次數(shù)就是1。
  2. 同一個(gè)值又被賦值給另一個(gè)變量,這個(gè)引用類(lèi)型值的引用次數(shù)加1.
  3. 當(dāng)包含這個(gè)引用類(lèi)型值的變量又被賦值成另一個(gè)值了,那么這個(gè)引用類(lèi)型值的引用次數(shù)減1.
  4. 當(dāng)引用次數(shù)變成0時(shí),說(shuō)明沒(méi)辦法訪(fǎng)問(wèn)這個(gè)值了。
  5. 當(dāng)垃圾收集器下一次運(yùn)行時(shí),它就會(huì)釋放引用次數(shù)是0的值所占的內(nèi)存。
循環(huán)引用時(shí)的問(wèn)題

但是循環(huán)引用的時(shí)候就會(huì)釋放不掉內(nèi)存。循環(huán)引用就是對(duì)象A中包含另一個(gè)指向?qū)ο驜的指針,B中也包含一個(gè)指向A的引用。因?yàn)镮E中的BOM、DOM的實(shí)現(xiàn)使用了COM,而COM對(duì)象使用的垃圾收集機(jī)制是引用計(jì)數(shù)策略。所以會(huì)存在循環(huán)引用的問(wèn)題。

解決:手工斷開(kāi)js對(duì)象和DOM之間的鏈接。賦值為null。IE9把DOM和BOM轉(zhuǎn)換成真正的JS對(duì)象了,所以避免了這個(gè)問(wèn)題因?yàn)檫@個(gè)過(guò)程中會(huì)出現(xiàn)一個(gè)循環(huán)引用的問(wèn)題!簡(jiǎn)單點(diǎn)來(lái)說(shuō)就是一個(gè)對(duì)象小a的屬性,引用了對(duì)象小b。小b對(duì)象也有一個(gè)屬性引用了小a,那么小a,小b互相引用對(duì)方,也就造成了循環(huán)引用的問(wèn)題啦。舉個(gè)栗子:

function test(){
    var a = {};
    var b = {};
    a.property = b;
    b.property = a;
}

這就是一個(gè)很明顯的循環(huán)引用了,小a和小b通過(guò)各自的屬性互相引用,導(dǎo)致了內(nèi)存無(wú)法釋放。(有那么一點(diǎn)點(diǎn)的感覺(jué)像死鎖。。。。)即使是再test()執(zhí)行完后,如果使用標(biāo)記清除是沒(méi)有問(wèn)題的,離開(kāi)環(huán)境的時(shí)候就會(huì)被清除。但是引用計(jì)數(shù)不行,因?yàn)檫@兩個(gè)對(duì)象的引用次數(shù)還是存在,不會(huì)變成0,所以其占用空間也不會(huì)清理,如果這個(gè)函數(shù)被調(diào)用多次,就會(huì)不斷有內(nèi)存被占用。造成了內(nèi)存泄露。

IE中有一部分對(duì)象并不是原生JavaScript對(duì)象。例如,其BOM和DOM中的對(duì)象就是使用C++以COM(Component Object Model)對(duì)象的形式實(shí)現(xiàn)的,而COM對(duì)象的垃圾收集機(jī)制采用的就是引用計(jì)數(shù)策略。因此即使IE的js引擎是用的標(biāo)記清除來(lái)實(shí)現(xiàn)的,但是js訪(fǎng)問(wèn)COM對(duì)象如BOM,DOM還是基于引用計(jì)數(shù)的策略的,也就是說(shuō)只要在IE中涉及到COM對(duì)象,也就會(huì)存在循環(huán)引用的問(wèn)題。比如說(shuō)第一種情況:一個(gè)DOM元素和一個(gè)原生的js對(duì)象之間的循環(huán)引用

var ele = document.getElementById("ele");
var obj = {};
obj.property = ele;
ele.property = obj;  //這種情況應(yīng)該手動(dòng)設(shè)置,在不適用的時(shí)候手工斷開(kāi)js和dom元素之間的連接
obj.property = null;
ele.property = null;

比如第二種情況是閉包的作用域鏈中包含著一個(gè)html元素,那么這個(gè)元素?zé)o法被銷(xiāo)毀

window.onload = function outerFunction(){ 
    var ele= document.getElementById("element");
    ele.onclick = function (){ 
        console.log(ele.id);
    }
}

上面這個(gè)代碼創(chuàng)建了一個(gè)作為ele元素處理程序的閉包,而這個(gè)閉包則又創(chuàng)建了一個(gè)循環(huán)引用。匿名函數(shù)中保存了一個(gè)outerFunction()的活動(dòng)對(duì)象的引用,因此就會(huì)導(dǎo)致無(wú)法減少ele的引用。可以改成下面這個(gè):

window.onload = function outerFunction(){
    var ele= document.getElementById("element");
    var id = ele.id;   
    ele.onclick = function (){
        console.log(id);
    }
    ele = null;
}

在上面的代碼中,通過(guò)把ele.id 的一個(gè)副本保存在一個(gè)變量中,并且在閉包中引用改變量消除了循環(huán)引用。

必須要記住:閉包會(huì)引用包含函數(shù)的整個(gè)活動(dòng)對(duì)象,而其中包含著elem。即使閉包不直接引用ele(比如上面的例子我們不用id),包含函數(shù)的多動(dòng)對(duì)象中也依舊會(huì)保存一個(gè)引用。因此,有必要把ele變量設(shè)置為null。這樣就能夠解除對(duì)DOM對(duì)象的引用,順利地減少其引用數(shù),確保正常回收其占用的內(nèi)存。

將變量設(shè)置為null意味著切斷變量與它此前引用的值之間的連接。當(dāng)垃圾收集器下次運(yùn)行時(shí),就會(huì)刪除這些值并回收它們占用的內(nèi)存。

垃圾回收機(jī)制的好處和壞處

好處:大幅簡(jiǎn)化程序的內(nèi)存管理代碼,減輕程序猿負(fù)擔(dān),并且減少因?yàn)殚L(zhǎng)時(shí)間運(yùn)轉(zhuǎn)而帶來(lái)的內(nèi)存泄露問(wèn)題。
壞處:自動(dòng)回收意味著程序猿無(wú)法掌控內(nèi)存。ECMAScript中沒(méi)有暴露垃圾回收的借口,我們無(wú)法強(qiáng)迫其進(jìn)行垃圾回收,更加無(wú)法干預(yù)內(nèi)存管理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,263評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,543評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,323評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,874評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,095評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,551評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,720評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,961評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,358評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,612評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,330評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,690評(píng)論 2 370

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

  • 和C#、Java一樣JavaScript有自動(dòng)垃圾回收機(jī)制,也就是說(shuō)執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存,在...
    ConRon閱讀 294評(píng)論 0 0
  • 為什么需要垃圾回收 由于字符串、對(duì)象和數(shù)組沒(méi)有固定大小,只有當(dāng)他們的大小已知時(shí),才能對(duì)他們進(jìn)行動(dòng)態(tài)的存儲(chǔ)分配。Ja...
    宇cccc閱讀 1,068評(píng)論 1 0
  • 「杜思仲說(shuō)」 我叫杜思仲。 三天前,我的女友失蹤了。臨走前,她告訴我,其實(shí)她是一朵修煉了六百年的桃花精,為了來(lái)人間...
    夏初啟閱讀 1,531評(píng)論 16 11
  • 有多少?zèng)]有雨的日子 能想起你 你的笑 是一輪明月 彎彎地 像一葉舟 從我的心里漂過(guò) 有多少溫暖的日子 捧著一杯茶 ...
    詩(shī)墨聞香閱讀 130評(píng)論 3 2
  • 小天的母親病了,躺在病床上。 其實(shí)母親的主治醫(yī)生在一個(gè)禮拜前已經(jīng)告知小天,母親時(shí)日已不多,讓小天以及其所有的家人做...
    小朱小文閱讀 661評(píng)論 0 1