首頁  >  文章  >  web前端  >  跟我學習javascript的垃圾回收機制與記憶體管理_javascript技巧

跟我學習javascript的垃圾回收機制與記憶體管理_javascript技巧

WBOY
WBOY原創
2016-05-16 15:30:331218瀏覽

一、垃圾回收機制—GC

Javascript具有自動垃圾回收機制(GC:Garbage Collecation),也就是說,執行環境會負責管理程式碼執行過程中使用的記憶體。

原理:垃圾收集器會定期(週期性)找出那些不在繼續使用的變量,然後釋放其記憶體。

JavaScript垃圾回收的機制很簡單:找出不再使用的變量,然後釋放掉其占用的內存,但是這個過程不是實時的,因為其開銷比較大,所以垃圾回收器會按照固定的時間間隔週期性的執行

不再使用的變數也就是生命週期結束的變量,當然只可能是局部變量,全域變數的生命週期直到瀏覽器卸載頁面才會結束。局部變數只在函數的執行過程中存在,而在這個過程中會為局部變數在堆疊或堆上分配對應的空間,以儲存它們的值,然後在函數中使用這些變量,直到函數結束,而閉包中由於內部函數的原因,外部函數並不能算是結束。

還是上程式說明吧:

function fn1() {
 var obj = {name: 'hanzichi', age: 10};
}

function fn2() {
 var obj = {name:'hanzichi', age: 10};
 return obj;
}

var a = fn1();
var b = fn2();

我們來看程式碼是如何執行的。首先定義了兩個function,分別稱為fn1和fn2,當fn1被呼叫時,進入fn1的環境,會開闢一塊記憶體存放物件{name: 'hanzichi', age: 10},而當呼叫結束後,出了fn1的環境,那麼該區塊記憶體會被js引擎中的垃圾回收器自動釋放;在fn2被呼叫的過程中,傳回的物件被全域變數b所指向,所以該區塊記憶體並不會被釋放。

這裡問題就出現了:到底哪個變數是沒有用的?所以垃圾收集器必須追蹤到底哪個變數沒用,而且對於不再有用的變數打上標記,以便將來收回其記憶體。用於標記的無用變數的策略可能因實現而有所區別,通常情況下有兩種實現方式:標記清除和引用計數。引用計數較不常用,標記清除較為常用。

二、標記清除

js中最常用的垃圾回收方式就是標記清除。當變數進入環境時,例如,在函數中宣告一個變量,就將這個變數標記為「進入環境」。從邏輯上講,永遠不能釋放進入環境的變數所佔用的內存,因為只要執行流進入對應的環境,就可能會使用它們。而當變數離開環境時,則標記為「離開環境」。

function test(){
 var a = 10 ; //被标记 ,进入环境 
 var b = 20 ; //被标记 ,进入环境
}
test(); //执行完毕 之后 a、b又被标离开环境,被回收。

  垃圾回收器在運行的時候會為儲存在記憶體中的所有變數加上標記(當然,可以使用任何標記方式)。然後,它會去掉環境中的變數以及被環境中的變數所引用的變數的標記(閉包)。而在此之後再被加上標記的變數將被視為準備刪除的變量,原因是環境中的變數已經無法存取這些變數了。最後,垃圾回收器完成記憶體清除工作,銷毀那些標記的值並回收它們所佔用的記憶體空間。

  到目前為止,IE、Firefox、Opera、Chrome、Safari的js實現使用的都是標記清除的垃圾回收策略或類似的策略,只不過垃圾收集的時間間隔互不相同。

三、引用計數

  引用計數的意思是追蹤記錄每個值被引用的次數。當宣告了一個變數並將一個引用型別值賦給該變數時,則這個值的引用次數就是1。如果同一個值又被賦給另一個變量,則該值的引用次數加1。相反,如果包含這個值所引用的變數又取得了另外一個值,則這個值的引用次數會減1。當這個值的引用次數變成0時,則表示沒有辦法再存取這個值了,因而就可以將其佔用的記憶體空間回收回來。這樣,當垃圾回收器下次再運作時,它就會釋放那些被引用次數為0的值所佔用的記憶體。

function test(){
 var a = {} ; //a的引用次数为0 
 var b = a ; //a的引用次数加1,为1 
 var c =a; //a的引用次数再加1,为2
 var b ={}; //a的引用次数减1,为1
}

  Netscape Navigator3是最早使用引用計數策略的瀏覽器,但很快它就遇到一個嚴重的問題:循環引用。循環引用指的是物件A中包含一個指向物件B的指針,而物件B中也包含一個指向物件A的引用。

function fn() {
 var a = {};
 var b = {};
 a.pro = b;
 b.pro = a;
}

fn();

  以上代码a和b的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存,如果fn函数被大量调用,就会造成内存泄露。在IE7与IE8上,内存直线上升。

我们知道,IE中有一部分对象并不是原生js对象。例如,其内存泄露DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;

  这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个属性名为o回指myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。

看上面的例子,有同学回觉得太弱了,谁会做这样无聊的事情,其实我们是不是就在做

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

这段代码看起来没什么问题,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法会引用外部环境中德变量,自然也包括obj,是不是很隐蔽啊。

解决办法

最简单的方式就是自己手工解除循环引用,比如刚才的函数可以这样

myObject.element = null;
element.o = null;


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

將變數設為null意味著切斷變數與它先前引用的值之間的連接。當垃圾回收器下次運行時,就會刪除這些值並回收它們佔用的記憶體。

要注意的是,IE9 並不存在循環引用導致Dom記憶體外洩問題,可能是微軟做了最佳化,或是Dom的回收方式已經改變

四、記憶體管理

1、何時觸發垃圾回收?

垃圾回收器週期性運行,如果分配的記憶體非常多,那麼回收工作也會很艱鉅,確定垃圾回收時間間隔就變成了一個值得思考的問題。 IE6的垃圾回收是根據記憶體分配量運行的,當環境中存在256個變數、4096個物件、64k的字串任意一種情況的時候就會觸發垃圾回收器工作,看起來很科學,不用按一段時間就呼叫一次,有時候會沒必要,這樣按需呼叫不是很好嗎?但是如果環境中就是有這麼多變數等一直存在,現在腳本如此複雜,很正常,那麼結果就是垃圾回收器一直在工作,這樣瀏覽器就沒法兒玩兒了。

微軟在IE7中做了調整,觸發條件不再是固定的,而是動態修改的,初始值和IE6相同,如果垃圾回收器回收的記憶體分配量低於程式佔用記憶體的15%,說明大部分記憶體不可回收,設的垃圾回收觸發條件過於敏感,這時候把臨街條件翻倍,如果回收的記憶體高於85%,表示大部分記憶體早就該清理了,這時候把觸發條件置回。這樣就使垃圾回收工作職能了很多

2、合理的GC方案

1)、Javascript引擎基礎GC方案是(simple GC):mark and sweep(標記清除),即:

  • (1)遍歷所有可存取的物件。
  • (2)回收已無法存取的物件。

2)、GC的缺陷

和其他語言一樣,javascript的GC策略也無法避免一個問題:GC時,停止回應其他操作,這是為了安全考慮。而Javascript的GC在100ms甚至以上,對一般的應用還好,但對於JS遊戲,動畫對連貫性要求比較高的應用,就麻煩了。這就是新引擎需要優化的點:避免GC造成的長時間停止反應。

3)、GC最佳化策略

David大叔主要介紹了2個最佳化方案,而這也是最主要的2個最佳化方案了:

(1)分代回收(Generation GC)
這個和Java回收策略思想是一致的。目的是透過區分「臨時」與「持久」對象;多回收「臨時對象」區(young generation),少回收「持久性對象」區(tenured generation),減少每次需遍歷的對象,從而減少每次GC的耗時。如圖:

這裡需要補充的是:對於tenured generation對象,有額外的開銷:把它從young generation遷移到tenured generation,另外,如果被引用了,那引用的指向也需要修改。

(2)增量GC
這個方案的想法很簡單,就是「每次處理一點,下次再處理一點,如此類推」。如圖:

這種方案,雖然耗時短,但中斷較多,帶來了上下文切換頻繁的問題。

因為每個方案都其適用場景和缺點,因此在實際應用中,會根據實際情況選擇方案。

例如:低 (物件/s) 比率時,中斷執行GC的頻率,simple GC更低;如果大量物件都是長期“存活”,則分代處理優勢也不大。

參考:

以上就是關於javascript的垃圾回收機制與記憶體管理的全部內容,希望對大家的學習有所幫助。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn