垃圾回收演算法

  • 2021 年 7 月 25 日
  • 筆記

v8的記憶體劃分

  • v8大體分為,垃圾回收在堆里進行。
  • 堆記憶體分多個模組:
    • New space

      大多數的對象開始都會被分配在這裡,這個區域相對較小但是垃圾回收特別頻繁,該區域被對半分為兩半(分為Semi space From 和 Semi space To )

    • Old space

      新生代中的對象在存活一段時間後就會被轉移到老生代記憶體區,相對於新生代該記憶體區域的垃圾回收頻率較低。

    • Large object space

      存放體積超越其他區域大小的對象,每個對象都會有自己的記憶體,垃圾回收不會移動大對象區。

    • Code space

      程式碼對象會被分配在這裡,唯一擁有執行許可權的記憶體區域。

    • Cells pace

    • Property cell space

    • Map space

  • 垃圾回收發生在新生代(New space)和老生代(Old space)中。

新生代和老生代的記憶體大小

根據作業系統不同:32位為0.7G,64位為1.4G

新生代 老生代
32位系統 34M 700M
64位系統 64M 1400M

但是最新版V14記憶體為2G

垃圾回收的演算法

新生代使用的是Scavenge(新生代互換演算法)

老生代現在使用的是Mark-Compact(標記整理演算法),以前使用Mark-Sweep(標記清除演算法),最古老的是引用計數演算法

Scavenge

過程:

每次新加入的變數都會放入from,當from中記憶體佔滿時會開始執行垃圾回收:對from中不用的記憶體回收,還存在引用的記憶體會被複制到to中,當這次垃圾回收結束的時候會出現from中為空,to中留下還存在引用的記憶體,這時會將from和to交換,最後就是from中留下有引用的記憶體,to中保持為空。

因為From和To中各佔一半,並且To始終不會存放,所以會浪費一半的空間。該演算法是使用空間換取時間,而老生代比新生代大很多,所以用該演算法不合理。


引用計數法

過程:

查看是否有其它的對象在引用該對象,如果沒有則把它清除。

該方法無法處理對象間的循環引用,容易造成記憶體泄漏。

在 IE 時代就被拋棄了。


Mark-Sweep

該演算法是早先使用的演算法,現在已經不被使用

過程:

垃圾回收會在內部構件一個根節點(可以看作是瀏覽器中的window,node中的gelobal)。首先對老生代中的所有對象進行一次廣度掃描,查找到有對根節點引用的對象時會把它標記,最後將沒有標記的對象垃圾回收。

該演算法沒有進行記憶體的碎片整理,所以以後再需要非陪一個大的對象時會提前觸發垃圾回收,為解決這個問題就產生了Mark-Compact(標記整理演算法)


Mark-Compact

在標記清除法的基礎上進行了優化:

  • 它在執行垃圾回收的開始把所有被標記記憶體移動到一起,然後清除未被標記的記憶體,這樣就解決了大對象存入時連續空間不足的問題。

  • 原先的Mark-Sweep演算法會在執行垃圾回收時進行一次廣度掃描,將所有有引用節點標記,這叫做全停頓標記法,因為JS是單執行緒的,所以一次垃圾回收的時間內全部標記對時間的花費會比較大;而現在則是使用增量標記法和三色標記法來進行標記。

    增量標記法和三色標記法: 將程式碼運行和垃圾回收之間的且換變得更加頻繁,每次垃圾回收的時候只查找標記為黑的節點,如果該節點的子節點有對根的引用,將子節點標黑,將當前節點標灰,父節點標白。直到引用樹全部標白後再進行垃圾回收操作。該模式將每次垃圾回收的標記拆分開插入程式碼的運行中,比原先的全停頓標記法體驗更好。


新生代晉陞到老生代

這個判斷很簡單:

  • 首先判斷該記憶體是否經歷過一次垃圾回收,沒經歷過的會放入to中。
  • 然後判斷這時to是否已經使用了25%(8M),如果沒有也會放入to中。
  • 兩個條件都判斷為true時會被晉陞到老生代中。