V8 內存管理(垃圾回收機制)
V8 內存管理(垃圾回收機制)
V8 也會申請內存,申請的內存又會分為堆內存和棧內存
1.1 棧
- 棧用于存放 JS 中的基本類型和引用類型指針
- 棧的空間是連續的,增加刪除只需要移動指針,操作速度非常快
- 棧的空間是有限的,當棧滿了,就會拋出一個錯誤
- 棧一般是在執行函數時創建的,在函數執行完畢后,棧就會被銷毀
1.2 堆
- 堆主要用于存儲 JS 中的引用類型
1.2.1 堆空間分類
1.2.1.1 新生代(new space)
- 新生代內存用于存放一些生命周期比較短的對象數據
1.2.1.2 老生代(old space)
- 老生代內存用于存放一些生命周期比較長的對象數據
- 當new space的對象進行兩個周期的垃圾回收后,如果數據還存在new space中,則將他們存放到old space中
- Old Space 使用標記清除和標記整理的方式進行垃圾回收
1.2.2 什么是垃圾
- 在程序運行過程中肯定會用到一些數據,這些數據會放在堆棧中,但是在程序運行結束后,這些數據就不會再被使用了,那些不再使用的數據就是垃圾
1.2.3 新生代的垃圾回收
- 新生代內存有兩個區域,分別是對象區域(from) 和 空閑區域(to)
- 新生代內存使用Scavenger 算法來管理內存,垃圾回收的入口廣度優先遍歷 From-Space 中的對象,從根對象出發,廣度優先遍歷所有能到達的對象,把存活的對象復制到 To-Space遍歷完成后,清空 From-SpaceFrom-Space 和 To-Space 角色互換
- 復制后的對象在 To-Space 中占用的內存空間是連續的,不會出現碎片問題
- 這種垃圾回收方式快速而又高效,但是會造成空間浪費(有 To-Space 空閑區域)
- 新生代的 GC 比較頻繁
- 新生代的對象轉移到老生代稱為晉升 Promote,判斷晉升的情況有兩種經過一次 GC 還存活的對象對象復制到 To-Space 時,To-Space 的空間達到一定的限制(超過 25%)
1.2.4 老生代的垃圾回收
V8 在老生代中的垃圾回收策略采用Mark-Sweep(標記清除)和 Mark-Compact(標記整理)相結合
1.2.4.1 Mark-Sweep(標記清除)
- 標記清除分為標記和清除兩個階段
- 在標記階段需要遍歷(深度優先遍歷)堆中的所有對象,并標記那些活著的對象,然后進入清除階段。在清除階段總,只清除沒有被標記的對象
- V8 采取的是黑色和白色來標記數據,垃圾收集之前,會把所有的數據設置為白色,用來標記所有的尚未標記的對象,然后會從 GC 根出發,以深度優先的方式把所有的能訪問到的數據都標記為黑色,遍歷結束后黑色的就是活的數據,白色的就是可以清理的垃圾數據
- 由于標記清除只清除死亡對象,而死亡對象在老生代中占用的比例很小,所以效率較高
- 標記清除有一個問題就是進行一次標記清楚后,內存空間往往是不連續的,會出現很多的內存碎片。如果后續需要分配一個需要內存空間較多的對象時,如果所有的內存碎片都不夠用,就會出現內存溢出的問題
1.2.4.2 Mark-Compact(標記整理)
- 標記整理正是為了解決標記清除所帶來的內存碎片的問題
- 標記整理在標記清除的基礎進行修改,將其的清除階段變為緊縮極端
- 在整理的過程中,將活著的對象向內存區的一段移動,移動完成后直接清理掉邊界外的內存
- 緊縮過程涉及對象的移動,所以效率并不是太好,但是能保證不會生成內存碎片,一般 10 次標記清理會伴隨一次標記整理
1.2.5 優化
- 在執行垃圾回收算法期間,JS 腳本需要暫停,這種叫 Stop the world(全停頓)
- 如果回收時間過長,會引起卡頓
- 性能優化把大任務拆分小任務,分步執行,類似 fiber將一些任務放在后臺執行,不占用主線程
1.2.5.1 Parallel(并行執行)
- 新生代的垃圾回收采取并行策略提升垃圾回收速度,它會開啟多個輔助線程來執行新生代的垃圾回收工作
1.2.5.2 增量標記
- 老生代因為對象又大又多,所以垃圾回收的時間更長,采用增量標記的方式進行優化
- 增量標記就是把標記工作分成多個階段,每個階段都只標記一部分對象,和主線程的執行穿插進行
- 為了支持增量標記,V8 必須可以支持垃圾回收的暫停和恢復,所以采用了黑白灰三色標記法黑色表示這個節點被 GC 根引用到了,而且該節點的子節點都已經標記完成了灰色表示這個節點被 GC 根引用到了,但子節點還沒被垃圾回收器標記處理,也表明目前正在處理這個節點白色表示此節點還沒未被垃圾回收器發現,如果在本輪遍歷結束時還是白色,那么這塊數據就會被收回
- 引入了灰色標記后,就可以通過判斷有沒有灰色節點來判斷標記是否完成了,如果有灰色節點,下次恢復的應該從灰色節點繼續執行
1.2.5.3 Write-barrier(寫屏障)
- 當黑色指向白色節點的時候,就會觸發寫屏障,這個寫屏障會把白色節點設置為灰色
1.2.5.4 Lazy Sweeping(惰性清理)
- 當增量標記完成后,如果內存夠用,先不清理,等 JS 代碼執行完慢慢清理
1.2.5.5 concurrent(并發回收)
- 其實增量標記和惰性清理并沒有減少暫停的總時間
- 并發回收就是主線程在執行過程中,輔助線程可以在后臺完成垃圾回收工作
- 標記操作全都由輔助線程完,清理操作由主線程和輔助線程配合完成
文章出自:??前端餐廳??,如有轉載本文請聯系前端餐廳ReTech今日頭條號。
github:??https://github.com/zuopf769??