JavaScript的垃圾回收
前段時間讀了一下<<JavaScript高級程序設(shè)計>>(書名很嚇人, 實際上作者寫得很好, 由淺入深), 了解了一下JavaScript的垃圾回收機制.
和Java一樣, JavaScript有自動垃圾回收機制.
JavaScript的垃圾回收機制很簡單:
找出不再使用的變量, 然后釋放掉其占用的內(nèi)存, 但是這個過程不是實時的, 因為開銷太大, 所以垃圾回收器會按照固定的時間周期性執(zhí)行.
變量生命周期
變量主要分為: 全局變量和局部變量.
全局變量的生命周期直至瀏覽器關(guān)閉頁面才會結(jié)束, 而局部變量只在函數(shù)執(zhí)行的過程中存在, 而在這個過程中會為局部變量在棧或堆上分配相應(yīng)的內(nèi)存空間, 以存儲它的值, 然后再在函數(shù)中使用這些變量, 直至函數(shù)結(jié)束(閉包中由于內(nèi)部函數(shù)的原因, 外部函數(shù)并不能算是結(jié)束了, 了解閉包可以看看JavaScript作用域鏈和閉包究竟是什么?)
一旦函數(shù)結(jié)束, 局部變量就沒有存在的必要了, 可以釋放它們占用的內(nèi)存.看似好像很簡單的工作, 為什么會有很大的開銷呢? 垃圾回收器需知道哪些變量有用, 哪些變量沒用, 對于不再有用的變量打上標(biāo)記, 以備將來回收. 用于標(biāo)記無用的策略很多, 常見的有兩種: 標(biāo)記清除和引用計數(shù).
標(biāo)記清除
這是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ù)一個列表等.
垃圾回收器會在運行的時候給存儲在內(nèi)存中的所有變量加上標(biāo)記, 然后去掉環(huán)境中的變量以及被環(huán)境變量所引用的變量(閉包), 在這些完成之后仍存在標(biāo)記的就是要刪除的變量, 因為環(huán)境中的變量已經(jīng)無法訪問到這些變量了, 然后垃圾回收器會清除這些標(biāo)記的變量所占的內(nèi)存空間.
大部份的瀏覽都是使用這種方式進(jìn)行垃圾回收, 區(qū)別在于如何標(biāo)記及垃圾回收間隔罷了.只有低版本的IE...
引用計數(shù)
在低版本的IE中經(jīng)常會出現(xiàn)內(nèi)存泄露, 很多時候是因為其采用引用計數(shù)方式進(jìn)行垃圾回收.
引用計數(shù)的策略是:
跟蹤記錄每個值被使用的次數(shù), 當(dāng)聲明一個變量并將一個引用類型賦值給該變量的時候, 這個值的引用次數(shù)
就加1, 如果該變量的值變成另一個, 則這個值的引用次數(shù)減1, 當(dāng)這個值的引用次數(shù)為1時, 說明沒有變量在使用, 這個值沒法被訪問了. 因此可以將其占用的內(nèi)存空間回收. 這樣垃圾回收器會在運行的時候清理掉引用次數(shù)為0的變量所占的內(nèi)存空間.
看起來不錯的方式, 為什么很少有瀏覽器采用, 還會帶來內(nèi)存泄露呢?
主要是因為這種方式?jīng)]法解決循環(huán)引用問題. 比如對象A有一個屬性指向?qū)ο驜, 而對象B也有一個屬性指向?qū)ο驛, 這樣的相互引用.
在IE中雖然JavaScript對象通過標(biāo)記清除的方式進(jìn)行垃圾回收, 但BOM與DOM對象卻是通過引用計數(shù)回收垃圾的,也就是說只要涉及BOM及DOM就很可能會出現(xiàn)循環(huán)引用的問題.如:
window.onload = function outFunction(){
var obj = document.getElementById("element");
obj.onclick = function innerFunction(){};
}
這段代碼看起來沒什么問題, 但是obj引用了document.getElementById("element"), 而document.getElementById("element")的onclick方法會引用外部環(huán)境中的變量, 自然也包括obj, 是不是很隱蔽?
解決辦法:
最簡單的辦法就是自己手工解除循環(huán)引用, 比如上面的例子:
window.onload = function outFunction(){
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ā)條件過于敏感, 這時就把臨界條件*2, 如果回收的內(nèi)存高于85%, 說明大部分內(nèi)存該清理了, 這時把觸發(fā)條件重置, 這樣就使得垃圾回收工作變得智能很多.
同Java一樣, 我們可以手工調(diào)用垃圾回收程序, 但由于其消耗大量資源, 而且手工調(diào)用的不會比瀏覽器判斷的準(zhǔn)確, 所以不推薦手工調(diào)用垃圾回收.