[譯] 前端面試:談談 JS 垃圾回收機制

前端小智發表於2019-04-01

譯者:前端小智

原文:javascript.info/garbage-col…

最近看到一些面試的回顧,不少有被面試官問到談談JS 垃圾回收機制,說實話,面試官會問這個問題,說明他最近看到一些關於 JS 垃圾回收機制的相關的文章,為了 B 格,就會順帶的問問。

最近看到一篇講 JS 垃圾回收的國外文章,覺得講得明白,所以就翻譯過來了,希望對你們有所幫助。

垃圾回收

JavaScript 中的記憶體管理是自動執行的,而且是不可見的。我們建立基本型別、物件、函式……所有這些都需要記憶體。

當不再需要某樣東西時會發生什麼? JavaScript 引擎是如何發現並清理它?

可達性

JavaScript 中記憶體管理的主要概念是可達性。

簡單地說,“可達性” 值就是那些以某種方式可訪問或可用的值,它們被保證儲存在記憶體中。

1. 有一組基本的固有可達值,由於顯而易見的原因無法刪除。例如:

  • 本地函式的區域性變數和引數

  • 當前巢狀呼叫鏈上的其他函式的變數和引數

  • 全域性變數

  • 還有一些其他的,內部的

這些值稱為根。

2. 如果引用或引用鏈可以從根訪問任何其他值,則認為該值是可訪問的。

例如,如果區域性變數中有物件,並且該物件具有引用另一個物件的屬性,則該物件被視為可達性, 它引用的那些也是可以訪問的,詳細的例子如下。

JavaScript 引擎中有一個後臺程式稱為[垃圾回收器][1],它監視所有物件,並刪除那些不可訪問的物件。

一個簡單的例子

下面是最簡單的例子:

// user 具有物件的引用
let user = {
  name: "John"
};
複製程式碼

圖片描述

這裡箭頭表示一個物件引用。全域性變數“user”引用物件 {name:“John”} (為了簡潔起見,我們將其命名為John)。John 的 “name” 屬性儲存一個基本型別,因此它被繪製在物件中。

如果 user 的值被覆蓋,則引用丟失:

user = null;
複製程式碼

圖片描述

現在 John 變成不可達的狀態,沒有辦法訪問它,沒有對它的引用。垃圾回收器將丟棄 John 資料並釋放記憶體。

兩個引用

現在讓我們假設我們將引用從 user 複製到 admin:

// user具有物件的引用
let user = {
  name: "John"
};

let admin = user;
複製程式碼

圖片描述

現在如果我們做同樣的事情:

user = null;
複製程式碼

該物件仍然可以通過 admin 全域性變數訪問,所以它在記憶體中。如果我們也覆蓋admin,那麼它可以被釋放。

相互關聯的物件

現在來看一個更復雜的例子, family 物件:

function marry (man, woman) {
  woman.husban = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
})
複製程式碼

函式 marry 通過給兩個物件彼此提供引用來“聯姻”它們,並返回一個包含兩個物件的新物件。

產生的記憶體結構:

圖片描述

到目前為止,所有物件都是可訪問的。

現在讓我們刪除兩個引用:

delete family.father;
delete family.mother.husband;
複製程式碼

圖片描述

僅僅刪除這兩個引用中的一個是不夠的,因為所有物件仍然是可訪問的。

但是如果我們把這兩個都刪除,那麼我們可以看到 John 不再有傳入的引用:

圖片描述

輸出引用無關緊要。只有傳入的物件才能使物件可訪問,因此,John 現在是不可訪問的,並將從記憶體中刪除所有不可訪問的資料。

垃圾回收之後:

圖片描述

無法訪問的資料塊

有可能整個相互連線的物件變得不可訪問並從記憶體中刪除。

源物件與上面的相同。然後:

family = null;
複製程式碼

記憶體中的圖片變成:

圖片描述

這個例子說明了可達性的概念是多麼重要。

很明顯,John和Ann仍然連結在一起,都有傳入的引用。但這還不夠。

“family”物件已經從根上斷開了連結,不再有對它的引用,因此下面的整個塊變得不可到達,並將被刪除。

內部演算法

基本的垃圾回收演算法稱為**“標記-清除”**,定期執行以下“垃圾回收”步驟:

  • 垃圾回收器獲取根並**“標記”**(記住)它們。
  • 然後它訪問並“標記”所有來自它們的引用。
  • 然後它訪問標記的物件並標記它們的引用。所有被訪問的物件都被記住,以便以後不再訪問同一個物件兩次。
  • 以此類推,直到有未訪問的引用(可以從根訪問)為止。
  • 除標記的物件外,所有物件都被刪除。

例如,物件結構如下:

圖片描述

我們可以清楚地看到右邊有一個“不可到達的塊”。現在讓我們看看**“標記並清除”**垃圾回收器如何處理它。

第一步標記根

圖片描述

然後標記他們的引用

圖片描述

以及子孫代的引用:

圖片描述

現在程式中不能訪問的物件被認為是不可訪問的,將被刪除:

圖片描述

這就是垃圾收集的工作原理。JavaScript引擎應用了許多優化,使其執行得更快,並且不影響執行。

一些優化:

  • 分代回收——物件分為兩組:“新物件”和“舊物件”。許多物件出現,完成它們的工作並迅速結 ,它們很快就會被清理乾淨。那些活得足夠久的物件,會變“老”,並且很少接受檢查。

  • 增量回收——如果有很多物件,並且我們試圖一次遍歷並標記整個物件集,那麼可能會花費一些時間,並在執行中會有一定的延遲。因此,引擎試圖將垃圾回收分解為多個部分。然後,各個部分分別執行。這需要額外的標記來跟蹤變化,這樣有很多微小的延遲,而不是很大的延遲。

  • 空閒時間收集——垃圾回收器只在 CPU 空閒時執行,以減少對執行的可能影響。

面試怎麼回答

1)問什麼是垃圾

一般來說沒有被引用的物件就是垃圾,就是要被清除, 有個例外如果幾個物件引用形成一個環,互相引用,但根訪問不到它們,這幾個物件也是垃圾,也要被清除。

2)如何檢垃圾

一種演算法是標記 標記-清除 演算法,還想說出不同的演算法可以參考[這裡][15]。

更深入一些的講解 newhtml.net/v8-garbage-…

還有一種牛逼的答法就是說看我的部落格,當然是要自己總結的部落格。

你的點贊是我持續分享好東西的動力,歡迎點贊!

一個笨笨的碼農,我的世界只能終身學習!

更多內容請關注公眾號[《大遷世界》][17]!

[譯] 前端面試:談談 JS 垃圾回收機制
[1]: en.wikipedia.org/wiki/Garbag… [2]: /img/bVbqbGS [3]: /img/bVbqcUf [4]: /img/bVbqcVf [5]: /img/bVbqcXI [6]: /img/bVbqcZj [7]: /img/bVbqcZ4 [8]: /img/bVbqc0I [9]: /img/bVbqc2u [10]: /img/bVbqd7y [11]: /img/bVbqd7V [12]: /img/bVbqd71 [13]: /img/bVbqd8a [14]: /img/bVbqd8A [15]: www.jianshu.com/p/a8a04fd00… [16]: www.fundebug.com/ [17]: user-gold-cdn.xitu.io/2019/4/6/16…

相關文章