JavaScript垃圾回收機制

和C#、Java一樣JavaScript有自動垃圾回收機制,也就是說執(zhí)行環(huán)境會負(fù)責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存,在開發(fā)過程中就無需考慮內(nèi)存分配及無用內(nèi)存的回收問題了。JavaScript垃圾回收的機制很簡單:找出不再使用的變量,然后釋放掉其占用的內(nèi)存,但是這個過程不是時時的,因為其開銷比較大,所以垃圾回收器會按照固定的時間間隔周期性的執(zhí)行。

變量生命周期

有同學(xué)看了上面就會問了,什么叫不再使用的變量?不再使用的變量也就是生命周期結(jié)束的變量,當(dāng)然只可能是局部變量,全局變量的生命周期直至瀏覽器卸載頁面才會結(jié)束。局部變量只在函數(shù)的執(zhí)行過程中存在,而在這個過程中會為局部變量在?;蚨焉戏峙湎鄳?yīng)的空間,以存儲它們的值,然后再函數(shù)中使用這些變量,直至函數(shù)結(jié)束(閉包中由于內(nèi)部函數(shù)的原因,外部函數(shù)并不能算是結(jié)束,了解閉包可以看看 JavaScript作用域鏈,JavaScript 閉包究竟是什么)。
  一旦函數(shù)結(jié)束,局部變量就沒有存在必要了,可以釋放它們占用的內(nèi)存。貌似很簡單的工作,為什么會有很大開銷呢?這僅僅是垃圾回收的冰山一角,就像剛剛提到的閉包,貌似函數(shù)結(jié)束了,其實還沒有,垃圾回收器必須知道哪個變量有用,哪個變量沒用,對于不再有用的變量打上標(biāo)記,以備將來回收。用于標(biāo)記無用的策略有很多,常見的有兩種方式

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

這是JavaScript最常見的垃圾回收方式,當(dāng)變量進(jìn)入執(zhí)行環(huán)境的時候,比如函數(shù)中聲明一個變量,垃圾回收器將其標(biāo)記為“進(jìn)入環(huán)境”,當(dāng)變量離開環(huán)境的時候(函數(shù)執(zhí)行結(jié)束)將其標(biāo)記為“離開環(huán)境”。至于怎么標(biāo)記有很多種方式,比如特殊位的反轉(zhuǎn)、維護(hù)一個列表等,這些并不重要,重要的是使用什么策略,原則上講不能夠釋放進(jìn)入環(huán)境的變量所占的內(nèi)存,它們隨時可能會被調(diào)用的到。
  垃圾回收器會在運行的時候給存儲在內(nèi)存中的所有變量加上標(biāo)記,然后去掉環(huán)境中的變量以及被環(huán)境中變量所引用的變量(閉包),在這些完成之后仍存在標(biāo)記的就是要刪除的變量了,因為環(huán)境中的變量已經(jīng)無法訪問到這些變量了,然后垃圾回收器相會這些帶有標(biāo)記的變量機器所占空間。
  大部分瀏覽器都是使用這種方式進(jìn)行垃圾回收,區(qū)別在于如何標(biāo)記及垃圾回收間隔而已,只有低版本IE,不出所料,又是IE。。。

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

在低版本IE中經(jīng)常會出現(xiàn)內(nèi)存泄露,很多時候就是因為其采用引用計數(shù)方式進(jìn)行垃圾回收。引用計數(shù)的策略是跟蹤記錄每個值被使用的次數(shù),當(dāng)聲明了一個變量并將一個引用類型賦值給該變量的時候這個值的引用次數(shù)就加1,如果該變量的值變成了另外一個,則這個值得引用次數(shù)減1,當(dāng)這個值的引用次數(shù)變?yōu)?的時候,說明沒有變量在使用,這個值沒法被訪問了,因此可以將其占用的空間回收,這樣垃圾回收器會在運行的時候清理掉引用次數(shù)為0的值占用的空間。
  看起來也不錯的方式,為什么很少有瀏覽器采用,還會帶來內(nèi)存泄露問題呢?主要是因為這種方式?jīng)]辦法解決循環(huán)引用問題。比如對象A有一個屬性指向?qū)ο驜,而對象B也有有一個屬性指向?qū)ο驛,這樣相互引用

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

這樣a和b的引用次數(shù)都是2,即使在test()執(zhí)行完成后,兩個對象都已經(jīng)離開環(huán)境,在標(biāo)記清除的策略下是沒有問題的,離開環(huán)境的就被清除,但是在引用計數(shù)策略下不行,因為這兩個對象的引用次數(shù)仍然是2,不會變成0,所以其占用空間不會被清理,如果這個函數(shù)被多次調(diào)用,這樣就會不斷地有空間不會被回收,造成內(nèi)存泄露。
  在IE中雖然JavaScript對象通過標(biāo)記清除的方式進(jìn)行垃圾回收,但BOM與DOM對象卻是通過引用計數(shù)回收垃圾的,也就是說只要涉及BOM及DOM就會出現(xiàn)循環(huán)引用問題??瓷厦娴睦樱型瑢W(xué)回覺得太弱了,誰會做這樣無聊的事情,其實我們是不是就在做

window.onload=function outerFunction(){ 
  varobj=document.getElementById("element");
  obj.onclick=function innerFunction(){}; 
};

這段代碼看起來沒什么問題,但是obj引用了document.getElementById("element"),而document.getElementById("element")的onclick方法會引用外部環(huán)境中德變量,自然也包括obj,是不是很隱蔽啊。
解決辦法
  最簡單的方式就是自己手工解除循環(huán)引用,比如剛才的函數(shù)可以這樣

window.onload=function outerFunction(){ 
  var obj =   document.getElementById("element"); 
  obj.onclick=function innerFunction(){}; 
  obj=null;
};

什么時候觸發(fā)垃圾回收
  垃圾回收器周期性運行,如果分配的內(nèi)存非常多,那么回收工作也會很艱巨,確定垃圾回收時間間隔就變成了一個值得思考的問題。IE6的垃圾回收是根據(jù)內(nèi)存分配量運行的,當(dāng)環(huán)境中存在256個變量、4096個對象、64k的字符串任意一種情況的時候就會觸發(fā)垃圾回收器工作,看起來很科學(xué),不用按一段時間就調(diào)用一次,有時候會沒必要,這樣按需調(diào)用不是很好嗎?但是如果環(huán)境中就是有這么多變量等一直存在,現(xiàn)在腳本如此復(fù)雜,很正常,那么結(jié)果就是垃圾回收器一直在工作,這樣瀏覽器就沒法兒玩兒了。
  微軟在IE7中做了調(diào)整,觸發(fā)條件不再是固定的,而是動態(tài)修改的,初始值和IE6相同,如果垃圾回收器回收的內(nèi)存分配量低于程序占用內(nèi)存的15%,說明大部分內(nèi)存不可被回收,設(shè)的垃圾回收觸發(fā)條件過于敏感,這時候把臨街條件翻倍,如果回收的內(nèi)存高于85%,說明大部分內(nèi)存早就該清理了,這時候把觸發(fā)條件置回。這樣就使垃圾回收工作職能了很多。
  同C# 、Java一樣我們可以手工調(diào)用垃圾回收程序,但是由于其消耗大量資源,而且我們手工調(diào)用的不會比瀏覽器判斷的準(zhǔn)確,所以不推薦手工調(diào)用垃圾回收。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 為什么需要垃圾回收 由于字符串、對象和數(shù)組沒有固定大小,只有當(dāng)他們的大小已知時,才能對他們進(jìn)行動態(tài)的存儲分配。Ja...
    宇cccc閱讀 1,130評論 1 0
  • 總結(jié): javascript使用的是標(biāo)記清除的方式: 這是JavaScript最常見的垃圾回收方式,當(dāng)變量進(jìn)入執(zhí)行...
    ironman_閱讀 188評論 0 0
  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虛擬機(JVM)垃圾回收器提供...
    簡欲明心閱讀 90,374評論 17 311
  • 組件 PercentRelativeLayout PercentFrameLayout 屬性 dependenci...
    markchan閱讀 283評論 0 0
  • 我們現(xiàn)代人生活中遇到的普遍困難是我沒有辦法去控制我自己去做那些并沒有用的消遣,或者說我沒有辦法把控我自己消遣的時間...
    蝸居的叉叉閱讀 301評論 0 0

友情鏈接更多精彩內(nèi)容