1. 程式人生 > >JavaScript的垃圾回收機制與記憶體洩漏

JavaScript的垃圾回收機制與記憶體洩漏

常用的兩種演算法:

引用計數(新版瀏覽器已棄用,棄用原因:會出現迴圈引用的情況,無法進行垃圾回收,導致記憶體洩漏)

標記清除

引用計數法

引用計數,顧名思義一個物件是否有指向它的引用,即看棧中是否有指向要釋放的該塊堆記憶體中的地址,如果沒有,則該塊記憶體是不需要的,可以進行釋放,即垃圾回收

下面引用大佬的一個簡短例子來說明情況

 1 // 建立一個物件person,他有兩個指向屬性age和name的引用
 2 var person = {
 3     age: 12,
 4     name: 'aaaa'
 5 };
 6 ​
 7 person.name = null; // 雖然name設定為null,但因為person物件還有指向name的引用,因此name不會回收
 8 ​
 9 var p = person; 
10 person = 1;         //原來的person物件被賦值為1,但因為有新引用p指向原person物件,因此它不會被回收
11 ​
12 p = null;           //原person物件已經沒有引用,很快會被回收
13  

 

缺點:引用計數有一個致命的問題,那就是迴圈引用

當兩個物件相互引用,儘管他們已不再使用,但是垃圾回收器不會進行回收,最終可能會導致記憶體洩露。

1 function cycle() {
2     var o1 = {};//1
3     var o2 = {};//1
4     o1.a = o2;//2
5     o2.a = o1; //2
6     return "cycle reference!"
7 }
8 ​
9 cycle();

 

cycle函式執行完成之後,物件o1o2實際上已經不再需要了,但根據引用計數的原則,他們之間的相互引用依然存在,因此這部分記憶體不會被回收。所以現代瀏覽器不再使用這個演算法。

但是IE依舊使用。

1 var div = document.createElement("div");
2 div.onclick = function() {
3     console.log("click");
4 };

上面的寫法很常見,但是上面的例子就是一個迴圈引用。

變數div有事件處理函式的引用,同時事件處理函式也有div的引用,因為div變數可在函式內被訪問,所以迴圈引用就出現了。

標記清除(常用)

文章裡寫的是:標記清除演算法將“不再使用的物件”定義為“無法到達的物件”。即從根部(在JS中就是全域性物件)出發定時掃描記憶體中的物件,凡是能從根部到達的物件,保留。那些從根部出發無法觸及到的物件被標記為不再使用,稍後進行回收。

我這裡個人理解:不在原型鏈上的,不能從全域性物件鏈找到的物件,會被認為是無法到達的物件(也可能我自己理解有誤,忘讀者指出),比如說下面這個例子

1 var a = {}  // 這裡的a是掛在全域性物件上的
2  
3 a = null  // 這裡a之前存放指向{}的地址變成了null
4 
5 // 此時{}是無法找到的,通過全域性物件找到a也無法到達{},因此{}會被垃圾回收

 

無法觸及的物件包含了沒有引用的物件這個概念,但反之未必成立。

所以上面的例子就可以正確被垃圾回收處理了。

所以現在對於主流瀏覽器來說,只需要切斷需要回收的物件與根部的聯絡,就能進行垃圾回收

下面還是引用大佬的例子

最常見的記憶體洩露一般都與DOM元素繫結有關:

email.message = document.createElement(“div”);
displayList.appendChild(email.message);
​
// 稍後從displayList中清除DOM元素
displayList.removeAllChildren();

上面程式碼中,div元素已經從DOM樹中清除,但是該div元素還繫結在email物件中,所以如果email物件存在,那麼該div元素就會一直儲存在記憶體中

參考文章:(https://www.muyiy.cn/blog/1/1.4.html#垃圾回收演算法)