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