JavaScript 內存管理 & 垃圾回收機制
標記清除
js 中最常用的垃圾回收方式就是標記清除。當變量進入環境時,例如,在函數中聲明一個變量,就將這個而變量標記為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占用的內存,因為只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記為“離開環境”。
引用計數
這是最簡單的垃圾收集算法。此算法把“對象是否不再需要”簡化定義為“對象有沒有其他對象引用它”。如果沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。
Netscape Navigator3 是最早使用引用計數策略的瀏覽器,但很快它就遇到了一個嚴重的問題:循環引用。循環引用指的是對象 A 中包含一個指向對象 B 的指針,而對象 B 中也包含一個指向對象 A 的引用。
|
1
2
3
4
5
6
7
|
function fn() {
var a = {};
var b = {};
a.pro = b;
b.pro = a;
}
fn();
|
以上代碼a和b的引用次數都是2,fn()執行完畢后,兩個對象都已經離開環境,在標記清除方式下是沒有問題的,但是在引用計數策略下,因為a和b的引用次數不為0,所以不會被垃圾回收器回收內存,如果fn函數被大量調用,就會造成內存泄漏。在IE7與IE8上,內存直線上升。
最簡單的方式就是自己手工解除循環引用,比如剛才的函數可以這樣
|
1
2
|
myObject.element =
null;
element.o =
null;
|
內存管理
1、什么時候觸發垃圾回收?
垃圾回收周期性運行,如果分配的內存非常多,那么回收工作也會很艱巨,確定垃圾回收時間間隔就變成了一個值得思考的問題。IE6 的垃圾回收是根據內存分配量運行的,當環境中存在 256 個變量、4096 個對象、64K 的字符串任意一種情況的時候就會觸發垃圾回收器工作,看起來很科學,不用按一段時間就調用一次,有時候會沒必要,這樣按需調用不是很好嘛?但是如果環境中就是有這么多變量一直存在,現在腳本如此復雜,很正常,那么結果就是垃圾回收器一直在工作,這樣瀏覽器就沒法玩了。
微軟在 IE7 中做了調整,觸發條件不再是固定的,而是動態修改的,初始值和IE6相同,如果垃圾回收器回收的內存分配量低於程序占用內存的 15%,說明大部分內存不可被回收,設的垃圾回收觸發條件過於敏感,這時候把臨界條件翻倍,如果回收的內存高於 85%,說明大部分內存早就該清理了,這時候把觸發條件置回。這樣就使垃圾回收工作智能了很多。
2、合理的 GC 方案
1)、JavaScript 引擎基礎 GC 方案是(simple GC):mark and sweep(標記清除),即:
- 遍歷所有可訪問的對象。
- 回收已不可訪問的對象。
2)、GC 的缺陷
和其他語言一樣,JavaScript 的 GC 策略也無法避免一個問題:GC 時,停止響應其他操作,這是為了安全考慮。而 JavaScript 的 GC 在 100ms 甚至以上,對一般的應用還好,但對於 JS 游戲,動畫連貫性要求比較高的應用,就麻煩了。這就是新引擎需要優化的點:避免 GC 造成的長時間停止響應。
總結
一般不用 setInterval,而用 setTimeout 的延時遞歸來代替 interval。
setInterval 會產生回調堆積,特別是時間很短的時候。
擴展
setInterval 有個很煩的地方就是當 js 主程序空閑的時候,執行代碼隊列里面的代碼的時候,如果此時候我們有一個問題,定時器是等到回調執行完,才開始計時進行下次循環呢?還是只要一次計時完畢,插入回調之后不管回調執不執行就開始計時呢?答案顯示是后者,這也就是我說 setInterval 坑的原因啊,因為這會出現一種情況,當我們插入回調的時候前隊列有別的代碼在執行,這時候回調肯定是不會執行的,因此如果這個時候無限定時時間到了會再次插入回調,這個時候如果發現隊列中的第一次回調沒有執行,那么再次插入的回調瀏覽器就默認取消,(這是以防出現回調連續執行多次的情況)但是這又引發了新的情況就是有些回調是不能取消掉的?
