淺談JavaScript的垃圾回收(V8為例)

前言

JavaSciptJava一樣都是由垃圾回收機制進行自動內存管理,與C/C++不同,JavaScript不需要時刻關注內存的分配和釋放問題

由於Node的出現,JavaScript已不僅僅是一門瀏覽器的腳本語言,Node極大地拓寬了JavaScript的應用場景,從客戶端延申到服務器端之後,這使得JavaScript使用者需要更加注意,在服務器端,內存管理的好壞將嚴重影響服務器的性能。

目前主流的JavaScript引擎是由Google開發的V8引擎V8性能優秀,使Google贏得了瀏覽器大戰。因此Node亦選用了V8。

V8的垃圾回收機制

主要的垃圾回收算法

V8的垃圾回數策略主要基於分代式垃圾回收機制。分代式垃圾回收機制是指將對象按照存活時間劃歸不同的世代,不同世代之後採用不同的回收算法。之所以這樣劃分是因為在本質上沒有任何一種回收算法能勝任所有場景

V8的內存分代主要分為新生代老生代兩代。其中新生代是指存活時間較短的對象,老生代中的對象則存活時間較長或常駐內存的對象

新生代

在分代的基础上,新生代中的對象主要通過Scavenge算法進行垃圾回數。在Scavenge的具體採用了Cherry算法。

Cherry算法採用複制&移動的方式實現垃圾回收。它將堆內存分為兩等份,每一部份的空間稱為semispace,兩個semispace只有一個處於使用中,另一個處理閑置狀態,使用中的叫做from空間,閑置的叫做to空間

當開始進行垃圾回收時,先檢查From空間中的存活對象,這些存活對象則會複制到To空間,而非存活的對象則會被直接釋放。然後From空間To空間的⻆色就會調換

對象晉升

當一個對象經歷過Scavenge回收,又或者To空間的內存占用比超過限制,則該對象將會由新生代晉升至老生代

老生代

對於老生代中的對象,V8主要採用Mark-SweepMark-Compact相結合的方式進行垃圾回收。Mark-Sweep是標記清除的意思,分為標記和清除兩個步驟,和Scavenge不同,Mark-Sweep不需要把內存劃分兩等份,在標記階段,Mark-Sweep會遍歷堆中的所有對象,并標記活著的對象,在清除階段,則只會清除沒有標記的對象。

Mark-Sweep會導致內存出現碎片化的現象,當需要分配一個大對象,會觸發一次不必要的垃圾回收。這時就有Mark-Compact,它跟Mark-Sweep差不多,區別只在對象在標記為死亡後,在整理的過程中,將活動的對象往一端移動,移動完成後,直接清理掉邊界外的內存。

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

3種垃圾回收算法的簡單對比

回數算法 Mark-Sweep Mark-Compact Scavenge
速度 中等 最慢 最快
空間開銷 少(有碎片) 少(無碎片) 雙倍空間(無碎片)
是否移動對象
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×