[譯] 通過垃圾回收機制理解 JavaScript 記憶體管理

wuzhengyan2015發表於2019-01-20

[譯] 通過垃圾回收機制理解 JavaScript 記憶體管理

照片來自 Unsplash 上的 Dlanor S

記憶體管理的主要目標是在需要的時候為系統動態地分配記憶體,然後釋放那些不再使用的物件的記憶體。像 C 和 C++ 這樣的語言有基本的記憶體分配函式,如 malloc(),而一些高階語言計算機體系結構(如 JavaScript)包含垃圾回收器來完成這項工作。它跟蹤記憶體分配並識別這些分配的記憶體是否不再使用,如果是就自動釋放。但是這種演算法不能完全決定記憶體是否仍被需要。因此,對於程式設計師來說,理解並決定一段特定的程式碼是否需要記憶體是非常重要的。讓我們瞭解一下 JavaScript 中的垃圾收集是如何工作的:

垃圾回收

JavaScript 引擎的垃圾回收器基本上是尋找記憶體中被刪除的無法訪問的物件。這裡我想解釋兩種垃圾回收演算法,如下所示:

  • 引用計數垃圾回收
  • 標記清除演算法

引用計數垃圾回收

這是一個簡單的垃圾回收演算法。這個演算法尋找那些沒有被引用的物件。如果一個物件沒有被引用,那麼該物件就可以被垃圾回收。

var obj1 = {
    property1: {
         subproperty1: 20
     }
};
複製程式碼

如上例所示,讓我們建立一個物件以理解這個演算法。這裡 obj1 引用了一個物件,其中的 property1 屬性也引用了一個物件。由於 obj1 具有物件的引用,因此這個物件不會被垃圾回收。

var obj2 = obj1;

obj1 = "some random text"
複製程式碼

現在,obj2 也引用了被 obj1 引用的同一個物件,但後來 obj1 被更新為了 "some random text",這導致 obj2 具有對該物件的唯一引用。

var obj_property1 = obj2.property1;
複製程式碼

現在 obj_property1 指向 obj2.property1,它引用了一個物件。因此該物件有兩個引用,如下所示:

  1. 作為 obj2 的屬性
  2. 在變數 obj_property1
obj2 = "some random text"
複製程式碼

通過更新為 "some random text" 來取消 obj2 對物件的引用。因此,之前的這個物件似乎沒有被引用了,所以它可以被垃圾回收。但是,由於 obj_property1 具有 obj2.property1 的引用,因此它不會被垃圾回收。

obj_property1 = null;
複製程式碼

當我們把 obj_property1 引用刪除,現在最初 obj1 指向的物件沒有引用了。所以現在它可以被垃圾回收。

這個演算法在哪裡失敗了?

function example() {
     var obj1 = {
         property1 : {
              subproperty1: 20
         }
     };
     var obj2 = obj1.property1;
     obj2.property1 = obj1;
     return 'some random text'
}

example();
複製程式碼

這裡引用計數演算法在函式呼叫之後不會從記憶體中刪除 obj1obj2 ,因為兩個物件都是相互引用的。


標記清除演算法

這個演算法查詢從根開始無法訪問的物件,這個根是 JavaScript 的全域性物件。該演算法克服了引用計數演算法的侷限性。沒有引用的物件是不可訪問的,但是反過來就不一定了。

var obj1 = {
     property1: 35
}
複製程式碼

[譯] 通過垃圾回收機制理解 JavaScript 記憶體管理

如上所示,我們可以看到建立的物件 obj1 如何從 ROOT 中訪問到的。

obj1 = null
複製程式碼

[譯] 通過垃圾回收機制理解 JavaScript 記憶體管理

現在,當我們將 obj1 的值設定為 null 時,該物件從根開始無法被訪問,因此它可以被垃圾回收。

該演算法從根開始,遍歷所有其他物件,同時標記它們。它進一步遍歷被遍歷的物件並標記它們。這個過程將被重複直到所有已被遍歷的節點沒有任何子節點和可遍歷的路徑。現在垃圾回收器會忽略所有可訪問物件,因為它們在遍歷時被標記。因此,所有未標記的物件顯然都是從根節點開始無法訪問的,這意味著它們可以被垃圾回收,稍後通過刪除這些物件釋放記憶體。讓我們通過下面的例子來試著理解一下:

[譯] 通過垃圾回收機制理解 JavaScript 記憶體管理

如上所示,這就是物件結構的樣子。我們可以注意到無法從根開始訪問的物件,但是讓我們嘗試瞭解在這種情況下標記清除演算法是如何工作的。

[譯] 通過垃圾回收機制理解 JavaScript 記憶體管理

演算法從根開始標記遍歷到的物件。上面的圖片中,我們可以注意到在物件上標記的綠色圓圈。這樣它就可以將物件標識為可從根開始可以訪問到。

[譯] 通過垃圾回收機制理解 JavaScript 記憶體管理

未被標記的物件是無法從根開始被訪問到的。因此它們可以被垃圾回收。

侷限性

物件必須顯式地設定為不可訪問。

自 2012 年以來,JavaScript 引擎已經使用此演算法來代替引用計數垃圾回收。

謝謝閱讀。


進一步閱讀:

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章