下面我就為大家帶來一篇全面了解JavaScirpt 的垃圾(garbage collection)回收機制。現在就分享給大家,也給大家做個參考。
一、垃圾回收機制—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箭頭(arrow)函數詳解解讀(圖文教學)
#以上是全面了解JavaScirpt 的垃圾(garbage collection)回收機制_基礎知識的詳細內容。更多資訊請關注PHP中文網其他相關文章!