垃圾回收演算法
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時會被晉升到老生代中。