V8中的垃圾回收算法

前言

本文內容來自於對《垃圾回收的算法與實現》內容的總結算法

V8是啥編程

這個你們都知道,V8 全稱是V8 JavaScript Engine,一個用C++寫的JavaScript引擎。markdown

垃圾回收又是啥編程語言

垃圾回收的英文是 Garbage Collection,簡稱GC。在代碼運行的過程當中,全部的數據都會存放在內存空間,若是沒有GC,開發者就必須手動進行內存管理,否則總有一天內存會被佔滿,進而致使進程崩潰,甚至系統崩潰。GC的做用就是讓計算機自動幫開發者進行內存管理,把內存中不須要再次使用的垃圾回收掉。函數

本文講的只是V8中的垃圾回收算法,而目前已有的垃圾回收算法遠不止這幾種,並且目前沒有完美的垃圾回收算法。spa

術語解釋

指針

GC時,相應的對象會被銷燬或者保留。這時候咱們能夠經過對象的指針去搜尋其餘對象。設計

指針通常指向對象的首地址。其實能夠簡單理解爲JS中的引用地址。3d

指動態存放對象的內存空間。JS中,對象存放在堆中。指針

mutator

mutator的實體是「應用程序」code

我的理解,在JS的場景下,它指的就是V8引擎。在V8運行的過程當中,會不斷的去生成對象,修改對象之間的引用關係(即更新了指針)。這些改變就會帶來垃圾,這時候就須要GC

活動對象/非活動對象

活動對象指堆中的能被mutator引用的對象。

非活動對象指堆中的不能被mutator引用的對象,即內存垃圾。

在下文中,屢次說起的一個名詞是根。什麼是根呢?術語上就是能夠經過mutator直接引用的對象。舉個例子

const obj = new Object(); // A對象
obj.child = new Object(); // B對象
複製代碼

第一行代碼中,咱們建立了一個對象A,它的引用地址賦值給了obj。第二行代碼中,咱們又建立了一個對象,它的引用地址賦值給了obj.child。那麼此時堆以下圖所示 這裏因爲咱們能夠經過全局變量obj找到A,因此A是活動對象,而後咱們又能夠經過A找到B,因此B也是活動對象。那麼這個全局變量obj就是一個根。

分代式垃圾回收

將內存空間中的對象分爲兩類,新生代和老年代。新建立的對象存放在新生代中,通過新生代中的GC後,某些對象依然存活。將存活了數次的對象看成老年代對象來處理。

新生代對象->老年代對象的過程,咱們稱之爲晉升。

GC的分類

GC有兩種類型,保守式和準確式。

保守式GC(Conservative GC)

GC時不能識別一個東西是否是指針時,這個時候的根被稱爲不明確的根。

舉個例子,咱們定義了全局變量a和局部變量obja是一個數值,obj是一個指針(引用地址),引用地址跟值a同樣,這個時候GC很難分辨出a究竟是指針仍是值。因而,保守處理,把它當成一個指針,當obj指向的對象應該被垃圾回收時,因爲全局變量a的存在,它不會被垃圾回收。這就是保守式GC

window.a = 0x00d0caf0; // 僞代碼,固然是會報錯的
const obj = new Object(); // 建立了對象,地址爲0x00d0caf0
複製代碼

在保守式GC的場景下,對象不可以被移動。由於若是移動了對象,意味着對象的引用地址會發生變化,那麼上面的obj相應的會重寫成移動後的引用地址,與此同時,全局變量a也會被重寫,這就很是恐怖了。因此對象不可以被移動。

準確式GC(Exact GC)

顧名思義,準確式GC可以正確識別出哪些內容是值,哪些內容是指針。要實現準確式GC,依賴於編程語言的處理,意味着成本的增長,這裏再也不贅述。

V8中的GC

V8中實現了準確式GC

GC算法方面採用了分代垃圾回收,結構以下。

GC複製算法(By Cheney)

GC複製算法將內存空間分爲FromTo,當From空間佔滿時,將From空間中的活動對象(劃重點)複製到To空間中,非活動對象回收掉,而後FromTo互換。顯而易見,FromTo空間的大小要徹底一致。

GC複製算法有不少種,好比 Robert R.Fenichel 和 Jerome C.Yochelson研究出來的和 C. J.Cheney 研究出來的。下面介紹的是Cheney研究出來的算法。

算法流程

在Cheney的複製算法中,算法流程以下

初始狀態

首先,複製全部從根直接引用的對象,BG。注意,新的B引用了From中的A,新的G仍是在引用From中的B(爲區分,寫做B1)和E

而後,搜索B1,發現引用了A,因而把A複製到To中,同時修正B中的指向。

接着,搜索G,把E複製到To中,而且G指向B1的指針換到了B

最後,搜索AE,發現沒有引用的對象,清空From,將FromTo空間互換,複製算法結束。

優勢

  • 吞吐量大
  • 不會發生碎片化
  • 沒有遞歸調用函數。cheney的算法中使用迭代的方式進行復制,這意味着沒有過多的消耗棧

缺點

  • 內存空間利用率小,很明顯,咱們每次只能使用一半的內存空間來分配對象
  • 不兼容保守式GC,由於移動了對象

觸發時機

From空間沒有分塊的時候

GC 標記-清除算法

本算法分爲兩個階段

  • 標記階段: 這個階段會遞歸遍歷堆中全部的活動對象,打上標記
  • 清除階段: 遍歷整個堆中全部對象,把全部沒有被標記的對象回收掉,堆越大,回收耗時越長

很明顯,通過這兩個階段後,不能利用的內存空得以再次被利用。

優勢:

  • 算法實現簡單
  • 能夠適用於保守式GC的場景

缺點:

屢次GC後會致使內存中出現碎片。碎片化的後果是,即便可用內存的總空間夠用,也會由於單個空間不夠用致使不可以分配內容(這個時候就要用到下面提到的標記-壓縮算法了)

假設內存空間一共5KB,下圖中A、B、C、D、E各佔了1KB,經歷一次GC後,B和D被回收,內存空間中剩餘2KB,此時分配一個大小爲2KB的對象到內存空間中,沒法分配,由於剩餘的2KB空間並不連續。

觸發時機

  • 老年代空間中某一個空間沒有分塊的時候
  • 老年代空間中分配了必定數量對象的時候(啓動新生代GC時會檢查)
  • 老年代空間中沒有新生代空間大小的分塊的時候(這個時候沒法保證新生代GC時的晉升)

GC 標記-壓縮算法

  • 標記階段:V8採用深度優先的方式進行標記,即標記了對象,隨後會去標記這個對象的子對象。深度優先遍歷時,通常採用遞歸操做,遞歸時,天然須要用到棧,在V8中,這個棧由V8自行生成。棧所用的空間是新生代的From空間。由於老年代GC以前,必然會執行新生代GC,這個時候From空間是空的,既然空都空了,不如就把棧放在這裏,不用白不用xd。

  • 壓縮階段:

壓縮前,能夠看到老年代空間中存在不少空白小方塊,即內存碎片

壓縮時,將內存對象按順序逐個移動到內存空間的前面

壓縮後,能夠看到空白小方塊已是連續的了,不存在內存碎片

優勢

  • 有效利用堆,不會像複製算法那樣只能利用半個堆,也不會存在內存碎片

缺點

  • 在算法過程當中,要不斷的去移動對象,成本相對於其它算法很是之高

觸發時機

  • 老年代空間中的碎片到達必定數量的時候

後記

一開始的時候,標題是《垃圾回收算法》,然而。。。瞭解深了以後發現,除了上面這些,還有引用計數法,增量式垃圾回收,RC Immix算法等等。即便是相同算法,不一樣設計者也有必定的區別。不一樣語言的GC算法實現上也有必定的區別。內容多的離譜,因而在標題前面加上了「V8中的」。

這個文涉及的東西實際開發中並不常見,並且術語比較多,不免有地方寫錯了,有朋友發現了的話,麻煩評論區留個言。

相關文章
相關標籤/搜索