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


摘要: 不是每個人都回答的出來...

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

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

垃圾回收

JavaScript 中的內存管理是自動執行的,而且是不可見的。我們創建基本類型、對象、函數……所有這些都需要內存。

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

可達性

JavaScript 中內存管理的主要概念是可達性。

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

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

  • 本地函數的局部變量和參數
  • 當前嵌套調用鏈上的其他函數的變量和參數
  • 全局變量
  • 還有一些其他的,內部的

這些值稱為根。

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

例如,如果局部變量中有對象,並且該對象具有引用另一個對象的屬性,則該對象被視為可達性, 它引用的那些也是可以訪問的,詳細的例子如下。

JavaScript 引擎中有一個后台進程稱為垃圾回收器,它監視所有對象,並刪除那些不可訪問的對象。

一個簡單的例子

下面是最簡單的例子:

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

這里箭頭表示一個對象引用。全局變量“user”引用對象 {name:“John”} (為了簡潔起見,我們將其命名為John)。John 的 “name” 屬性存儲一個基本類型,因此它被繪制在對象中。

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

user = null;

現在 John 變成不可達的狀態,沒有辦法訪問它,沒有對它的引用。垃圾回收器將丟棄 John 數據並釋放內存。

代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

兩個引用

現在讓我們假設我們將引用從 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;

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

但是如果我們把這兩個都刪除,那么我們可以看到 Jo加粗文字hn 不再有傳入的引用:

輸出引用無關緊要。只有傳入的對象才能使對象可訪問,因此,John 現在是不可訪問的,並將從內存中刪除所有不可訪問的數據。

垃圾回收之后:

無法訪問的數據塊

有可能整個相互連接的對象變得不可訪問並從內存中刪除。

源對象與上面的相同。然后:

family = null;

內存中的圖片變成:

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

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

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

內部算法

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

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

例如,對象結構如下:

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

第一步標記根

然后標記他們的引用

以及子孫代的引用:

現在進程中不能訪問的對象被認為是不可訪問的,將被刪除:

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

一些優化:

  • 分代回收——對象分為兩組:“新對象”和“舊對象”。許多對象出現,完成它們的工作並迅速結 ,它們很快就會被清理干凈。那些活得足夠久的對象,會變“老”,並且很少接受檢查。
  • 增量回收——如果有很多對象,並且我們試圖一次遍歷並標記整個對象集,那么可能會花費一些時間,並在執行中會有一定的延遲。因此,引擎試圖將垃圾回收分解為多個部分。然后,各個部分分別執行。這需要額外的標記來跟蹤變化,這樣有很多微小的延遲,而不是很大的延遲。
  • 空閑時間收集——垃圾回收器只在 CPU 空閑時運行,以減少對執行的可能影響。

面試怎么回答

1)問什么是垃圾

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

2)如何檢垃圾

一種算法是標記 標記-清除 算法,還想說出不同的算法可以參考這里

更深入一些的講解 V8 之旅: 垃圾回收器

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

關於Fundebug

Fundebug專注於JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟件、百姓網等眾多品牌企業。歡迎大家免費試用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM