javascript 垃圾回收演算法瞭解一下

程式設計師解決師發表於2018-06-12

我們通常理解的 javascript 垃圾回收機制都停留在表面,"會釋放不被引用變數記憶體",最近在讀《深入淺出node.js》的書,詳細瞭解了下 v8 垃圾回收的演算法,記錄了一些學習筆記。

敲黑板:v8引擎的垃圾回收演算法

javascript 垃圾回收演算法瞭解一下

V8的垃圾回收策略主要基於分代式垃圾回收機制,現代的垃圾回收演算法中按物件的存活時間將記憶體的垃圾回收進行不同的分代,然後分別對不同分代的記憶體施以更高效的演算法。在V8中,主要將記憶體分為新生代和老生代兩代。新生代中的物件為存活時間較短的物件, 老生代中的物件為存活時間較長或常駐記憶體的物件。

Scavenge演算法

在分代的基礎上,新生代中的物件主要通過Scavenge演算法進行垃圾回收,在Scavenge的具體 實現中,主要採用了Cheney演算法

javascript 垃圾回收演算法瞭解一下

Cheney 演算法是一種採用複製的方式實現的垃圾回收演算法。它將堆記憶體一分為二,每一部分空間稱為 semispace。在這兩個 semispace 空間中,只有一個處於使用中,另一個處於閒置狀態。處於使用狀態的 semispace 空間稱為 From 空間,處於閒置狀態的空間稱為 To 空間。當我們分配物件時,先是在 From 空間中進行分配。當開始進行垃圾回收時,會檢查 From 空間中的存活物件,這 些存活物件將被複制到 To 空間中,而非存活物件佔用的空間將會被釋放。完成複製後,From 空 間和To空間的角色發生對換。 簡而言之, 在垃圾回收的過程中, 就是通過將存活物件在兩個 semispace 空間之間進行復制。

Mark-Sweep & Mark-Compact

Scavenge演算法通過犧牲空間換時間的演算法非常適合生命週期短的新生代,但是,當一個物件經過多次複製,生命週期較長的時候或則To空間不足的時候,物件會被分配到進入到老生代中,需要採用新的演算法進行垃圾回收。

javascript 垃圾回收演算法瞭解一下

Mark-Sweep 並不將記憶體空間劃分為兩半,所以不存在浪費一半空間的行為。與 Scavenge 複製活著的物件不同, Mark-Sweep 在標記階段遍歷堆中的所有物件,並標記活著的物件,在隨後的清除階段中,只清除沒有被標記的物件。可以看出,Scavenge 中只複製活著的物件,而 Mark-Sweep 只清理死亡物件。

Mark-Sweep 在進行一次標記清除回收後,記憶體空間會出現不連續的狀態。這種記憶體碎片會對後續的記憶體分配造成問題,因為很可能出現需要分配一個大物件的情況,這時所有的碎片空間都無法完成此次分配,就會提前觸發垃圾回收,而這次回收是不必要的。Mark-Compact 物件在標記為死亡後,在整理的過程中,將活著的物件往一端移動,移動完成後,直接清理掉邊界外的記憶體

增量標記

為了避免出現 JavaScript 應用邏輯與垃圾回收器看到的不一致的情況,垃圾回收的 3 種基本演算法都需要將應用邏輯暫停下來,待執行完垃圾回收後再恢復執行應用邏輯,這種行為被稱為“全停頓",長時間的"全停頓"垃圾回收會讓使用者感受到明顯的卡頓,帶來體驗的影響。以1.5 GB的垃圾回收堆記憶體為例,V8做一次小的垃圾回收需要50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。這是垃圾回收中引起JavaScript執行緒暫停執行的時間,在 這樣的時間花銷下,應用的效能和響應能力都會直線下降。

為了降低全堆垃圾回收帶來的停頓時間,V8先從標記階段入手,將原本要一口氣停頓完成的動作改為增量標記(incremental marking),也就是拆分為許多小“步進”,每做完一“步進” 就讓 JavaScript 應用邏輯執行一小會兒,垃圾回收與應用邏輯交替執行直到標記階段完成

擴充套件閱讀

JavaScript 記憶體洩漏教程

精讀《深入淺出Node.js》

《深入淺出Node.js》PDF

思維導圖下載地址

相關文章