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

  • 2019 年 10 月 3 日
  • 筆記

常用的兩種演算法:

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

標記清除

引用計數法

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

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

 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#垃圾回收演算法)