V8引擎的垃圾回收策略

V8 的垃圾回收策略主要基於分代式垃圾回收機制。所謂分代式,就是將內存空間分爲新生代和老生代兩種,而後採用不一樣的回收算法進行回收。html

新生代空間

新生代空間中的對象爲存活時間較短的對象,大多數的對象被分配在這裏,這個區域很小可是垃圾回特別頻繁 。算法

它將堆內存一分爲二,每一部分空間稱爲 semispace,其中一個處於使用狀態(from 空間),另外一個處於閒置狀態(to 空間)閉包

對於新產生的對象,將從 from 空間中分配內存 。函數

新生代分配內存很是容易,咱們只須要保存一個指向內存區的指針,不斷根據新對象的大小進行遞增便可。當該指針到達了新生代內存區的末尾,就會觸發一次垃圾回收。性能

新生代的垃圾回收採用 Scavenge 算法 ,其工做原理以下:spa

首先檢查 from 空間,將存活對象複製到 to 空間,非存活對象將會被釋放。完成複製後,from 空間和 to 空間角色發生轉換。新產生的對象始終從 from 空間中分配內存,to 空間則處於閒置狀態。當再次進行垃圾回收時,也會執行和第一次一樣的操做,若是存在如下兩種狀況,存活對象就會被複制到老生代空間中,這個過程稱爲對象晉升線程

  • 存活對象已經經歷過一次 Scavenge 回收 。
  • to 空間內存佔用比例超過 25% (保證下次新對象有足夠的空間可分配)

老生代空間

老生代空間中的對象爲存活時間長或常駐內存對象,大多數重新生代晉升的對象會被移動到這裏。指針

老生代佔用內存較多,若是使用 Scavenge算法,不只會浪費一半空間,複製如此大塊的內存消耗時間將會很長,因此 Scavenge 算法顯然不適合。htm

V8 對於老生代中的垃圾回收,採用 Mark-Sweep (標記清除) 和 Mark-Compact(標記整理) 相結合 。對象

【1】Mark-Sweep

Mark-Sweep 分爲 標記 和 清除 兩個階段 。

在標記階段須要遍歷堆中的全部對象,並標記那些活着的對象,而後進入清除階段。在清除階段,只清除沒有被標記的對象。因爲標記清除只清除死亡對象,而死亡對象在老生代中佔用的比例很小,因此效率較高。

標記清除存在的問題是,進行一次標記清除後,內存空間每每是不連續的,會出現不少的內存碎片。若是後續須要分配一個須要內存空間較多的對象時,若是全部的內存碎片都不夠用,將會使得V8沒法完成此次分配,提早觸發垃圾回收。

【2】Mark-Compact

標記整理正是爲了解決標記清除所帶來的內存碎片的問題。標記整理在標記清除的基礎進行修改,將其的清除階段變爲緊縮極端。在整理的過程當中,將活着的對象向內存區的一段移動,移動完成後直接清理掉邊界外的內存。緊縮過程涉及對象的移動,因此效率並非太好,可是能保證不會生成內存碎片。  

三種回收策略比較

從圖中能夠看出,在 Mark-Sweep 和 Mark-Compact 之間,因爲 Mark-Compact 須要移動對象,因此它的執行速度最慢。

因此在取捨上,V8 主要使用 Mark-Sweep,在空間不足以對新生代中晉升過來的對象進行分配時才使用 Mark-Compact 。

垃圾回收引發的性能問題

爲了不出現 JavaScript 應用邏輯 與 垃圾回收操做 產生不一致的衝突,垃圾回收的三種基本算法都須要將應用邏輯暫停下來,待垃圾回收完成後,再恢復執行應用邏輯,這種行爲被稱爲全停頓 。

按官方說法,以 1.5G 的垃圾回收堆內存爲例,V8 作一次小的垃圾回收須要 50ms 以上,作一次非增量式垃圾回收甚至須要 1s 以上。這是垃圾回收中引發的 JavaScript 線程暫停執行時間,在這樣的時間花銷下,應用性能和響應能力都會直線降低。

在 V8 的分代式垃圾回收中,一次小垃圾回收只收集新生代,因爲新生代默認配置的較小,且其中活動對象一般較少,因此即使它是全停頓,影響也不大。

但 V8 的老生代一般配置較大,且存活對象較多,全堆垃圾回收的標記、清理、整理等動做形成的停頓就會比較嚴重。

爲下降全堆垃圾回收而致使的停頓時間,V8 作了如下改善措施:

【1】限制堆內存大小

  •  新生代:64 位系統 和 32 位系統分別爲 32M 和 16 M (from 和 to 空間各佔一半)
  •  老生代:64 位系統 和 32 位系統分別爲 1400M 和 700 M

【2】增量式垃圾回收

V8 先從標記階段入手,將原來一口氣停頓完成的動做改成 增量標記(Incremental Marking),也就是拆分爲許多小步進,每作完一步進,就讓 JavaScript 應用邏輯執行一小會兒,垃圾回收與應用邏輯交替執行,直到標記階段完成。V8 後續還引入 Lazy Sweep(延遲清除)、Incremental Compaction (增量式整理),讓清理與整理動做也變成增量式的。同時還計劃引入並行標記與並行整理,進一步利用多核性能來下降每次停頓的時間。

垃圾回收的觸發條件

 做用域: 能造成做用域的函數調用、with 語句 以及 全局做用域。

 閉包:  V8 沒法主動回收內存中的閉包引用和全局變量引用。

內存泄漏

一般,形成內存泄漏的緣由有以下幾個:

  • 隊列消費不及時
  • 做用域未釋放

 

原創發佈 @一像素 2017.08

 

參考文獻:

[1]  樸靈,深刻淺出Node.js,人民郵電出版社,2013

相關文章
相關標籤/搜索