咱們一般理解的 javascript 垃圾回收機制都停留在表面,"會釋放不被引用變量內存",最近在讀《深刻淺出node.js》的書,詳細瞭解了下 v8 垃圾回收的算法,記錄了一些學習筆記。
V8的垃圾回收策略主要基於分代式垃圾回收機制,現代的垃圾回收算法中按對象的存活時間將內存的垃圾回收進行不一樣的分代,而後分別對不一樣分代的內存施以更高效的算法。在V8中,主要將內存分爲新生代和老生代兩代。新生代中的對象爲存活時間較短的對象, 老生代中的對象爲存活時間較長或常駐內存的對象。javascript
在分代的基礎上,新生代中的對象主要經過Scavenge算法進行垃圾回收,在Scavenge的具體 實現中,主要採用了Cheney算法
Cheney 算法是一種採用複製的方式實現的垃圾回收算法。它將堆內存一分爲二,每一部分空間稱爲 semispace。在這兩個 semispace 空間中,只有一個處於使用中,另外一個處於閒置狀態。處於使用狀態的 semispace 空間稱爲 From 空間,處於閒置狀態的空間稱爲 To 空間。當咱們分配對象時,先是在 From 空間中進行分配。當開始進行垃圾回收時,會檢查 From 空間中的存活對象,這 些存活對象將被複制到 To 空間中,而非存活對象佔用的空間將會被釋放。完成複製後,From 空 間和To空間的角色發生對換。 簡而言之, 在垃圾回收的過程當中, 就是經過將存活對象在兩個 semispace 空間之間進行復制。html
Scavenge算法經過犧牲空間換時間的算法很是適合生命週期短的新生代,可是,當一個對象通過屢次複製,生命週期較長的時候或則To空間不足的時候,對象會被分配到進入到老生代中,須要採用新的算法進行垃圾回收。
Mark-Sweep 並不將內存空間劃分爲兩半,因此不存在浪費一半空間的行爲。與 Scavenge 複製活着的對象不一樣, Mark-Sweep 在標記階段遍歷堆中的全部對象,並標記活着的對象,在隨後的清除階段中,只清除沒有被標記的對象。能夠看出,Scavenge 中只複製活着的對象,而 Mark-Sweep 只清理死亡對象。java
Mark-Sweep 在進行一次標記清除回收後,內存空間會出現不連續的狀態。這種內存碎片會對後續的內存分配形成問題,由於極可能出現須要分配一個大對象的狀況,這時全部的碎片空間都沒法完成這次分配,就會提早觸發垃圾回收,而此次回收是沒必要要的。Mark-Compact 對象在標記爲死亡後,在整理的過程當中,將活着的對象往一端移動,移動完成後,直接清理掉邊界外的內存node
爲了不出現 JavaScript 應用邏輯與垃圾回收器看到的不一致的狀況,垃圾回收的 3 種基本算法都須要將應用邏輯暫停下來,待執行完垃圾回收後再恢復執行應用邏輯,這種行爲被稱爲「全停頓",長時間的"全停頓"垃圾回收會讓用戶感覺到明顯的卡頓,帶來體驗的影響。以1.5 GB的垃圾回收堆內存爲例,V8作一次小的垃圾回收須要50毫秒以上,作一次非增量式的垃圾回收甚至要1秒以上。這是垃圾回收中引發JavaScript線程暫停執行的時間,在 這樣的時間花銷下,應用的性能和響應能力都會直線降低。git
爲了下降全堆垃圾回收帶來的停頓時間,V8先從標記階段入手,將本來要一口氣停頓完成的動做改成增量標記(incremental marking),也就是拆分爲許多小「步進」,每作完一「步進」 就讓 JavaScript 應用邏輯執行一小會兒,垃圾回收與應用邏輯交替執行直到標記階段完成github