JavaScript垃圾收集機制及內存泄漏問題


JavaScript具有自動垃圾收集機制,也就是說執行環境會負責管理代碼執行過程中使用的內存。在C,C++之類的語言中,開發人員的一項基本任務就是手動跟蹤內存使用情況。這會造成很多問題,在編寫JavaScript代碼時,開發人員不用擔心內存分配以及無用內存的回收問題,JS完全實現了自動管理。

垃圾收集機制原理:

找出那些不再繼續使用的變量,然后釋放其占用的內存。為此,垃圾收集器會按照固定的時間間隔(或代碼執行中預定的收集時間)周期性的執行這一函數。在瀏覽器實現,通常使用以下兩種策略:

(一)標記清除

JS中最常用的垃圾收集方式即為標記清除。當變量進入環境(例如:在函數中聲明變量)時,就將這個變量標記為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占有的內存,因為只要執行流進入相應的環境,就可能會用到它們,而當變量離開環境時,將其標記為“離開環境”。

垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記(可以使用任何標記方式)。然后,它會去掉環境中的變量以及被環境中變量引用的變量的標記。在此之后再被加上標記的變量將被視為准備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最后,垃圾收集器完成垃圾收集策略(或類似的策略),只不過垃圾收集的時間間隔有所不同。

(二)標記清除

另一種不太常見的垃圾收集策略引用計數。
引用計數的含義是跟蹤記錄每一個值被引用的次數。當聲明了一個變量並一個引用類型的值賦給該變量時,則這個值的引用次數就加1,如果同一個值又被賦給另一個變量,則該值的引用次數加1。相反,如果包含對這個值引用的變量又取得了零外一個值,則這個值的引用次數減1。當這個值的引用次數變為0時,它就會釋放那些引用次數為0的值所占的內存。
但是這種策略面臨一個很嚴重的問題:循環引用
循環引用:指對象A包含一個指向對象B的指針,而對象B中也包含一個指向對象A的引用
例如:


function problem(){
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}

objectA和objectB通過各自的屬性相互引用,也就是說,這兩個對象的引用次數都是2。在采用標記清除策略的實現中,由於函數執行之后,這兩個對象都離開了作用域,因此這種相互引用不存在問題。但是在采用引用計數策略的實現中,當函數執行完畢之后,objectA和objectB還將繼續存在因為它們的引用次數永遠不是0。假如這個函數被重復調用多次,就會導致大量內存得不到回收。
在IE中,有一部分對象並不是原生的JavaScript對象。例如:其BOM和DOM中的對象就是使用C++以COM對象形式實現的,而COM對象的垃圾收集機制采用的就是引用計數策略。即使IE的JavaScript引擎是使用標記清除策略來實現的,但是JavaScript訪問到的COM對象依然是基於引用計數策略的。也就是說只要IE中涉及COM對象,就存在循環引用的問題。下面舉例說明存在的問題以及解決方法:

案例:
var element = document.getElementById("some_element");//獲取到一個對象,屬於引用型
var myObject = new Object();
myObject.element = element;
element.someObject = myObject();

這個例子中,在一個DOM 元素和一個原生JavaScript對象之間創建了循環引用。其中,變量myObject有一個名為element的屬性指向element對象,而變量element也有一個屬性名叫someObject回指myObject.由於存在這個循環引用,就是將這個例子中的DOM從頁面中移除,它也永遠不會被回收。
為了解決這個問題,我們可以在不使用它們的時候手工斷開原生JavaScript對象與DOM元素之間的連接。
例如可以使用下面的代碼消除前面例子創建的循環引用:

myObject.element = null;
element.someObject = null;

將變量的值設置為null意味着切斷變量與它此前引用值之間的連接。當垃圾收集器下次運行時,就會刪除這些值並回收它們占用的內存。

內存泄漏

由於IE9之前的版本對JS對象和COM對象使用不同的垃圾收集例程,因此閉包在IE的這些版本中會導致一些特殊的問題。如果閉包的作用域鏈保存着一個HTML元素,那么就意味着該元素無法被銷毀,例如:

function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}

以上代碼創建了一個作為element元素事件處理程序的閉包,而這個閉包則又創建了一個循環引用。由於匿名函數中保存了一個對assignHandler()的活動對象的引用,因此就導致無法減少element 的引用次數。只要匿名函數存在,element的引用次數至少也為1,因此它所占用的內存就永遠不會被回收。處理這個問題的方法只需要小小修改一下代碼:

function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};

以上代碼通過吧element id 的一個副本保存在變量中,並且在閉包中引用該變量消除了循環引用。但是僅僅做這一步還不能解決內存泄漏的問題。閉包會引用包含函數的整個活動對象,而其中包含element對象。即使閉包不直接引用element,包含函數的活動對象中也依然會保存一個引用。因此有必要將element變量設置為null,這樣就能解除對DOM對象的引用,順利減少其引用次數,確保正常回收其占用的內存。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com