日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

V8 —— 你需要知道的垃圾回收机制

發布時間:2025/6/16 编程问答 65 豆豆
生活随笔 收集整理的這篇文章主要介紹了 V8 —— 你需要知道的垃圾回收机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

V8 blog近日發布了文章描述了“并發標記”的新技術,提升標記過程的效率。并發標記是一個主要用新的平行和并發的垃圾收集器替換舊的垃圾回收器的項目,現在Chrome 64和Node.js v10已經默認啟用并發標記。講解之前我們先回顧一下基本知識點。


基本概念

弱分代假設(The Weak Generational Hypothesis)

  • 多數對象的生命周期短
  • 生命周期長的對象,一般是常駐對象
  • V8的GC也是基于假設將對象分為兩代: 新生代和老生代。對不同的分代執行不同的算法可以更有效的執行垃圾回收。


    新生代與老生代

    新生代包括一個New Space,老生代包括: Old Space, Code Space和Map Space,Large Object Space。64位環境下的V8引擎的新生代內存大小32MB、老生代內存大小為1400MB,而32位則減半,分別為16MB和700MB。對于新生代的對象,采用空間換取時間的Scavenge算法, 盡可能快的回收內存。如果對象經歷了2次GC還依然堅挺,就會在第二次回收時晉升為老生代(準確的說是保存在Old Space中)。而老生代的GC采取Mark-Sweep的算法,并使用Mark-Sweep解決內存碎片的問題。


    Scavenge算法

    對于新生代對象,采用Scavenge算法來回收。簡單來說,將內存的空間分為兩個semispace,同一時刻只有一個空間處于使用中。使用中的叫做 to space,不被使用的叫做 from space。分配對象時,先在From空間分配,垃圾回收時檢查(寬度優先)From空間的存活對象,將存活對象復制到To空間,清理非存活對象,復制后,空間身份發生對調。


    Mark-Sweep算法

    處理老生代對象時,采用深度優先掃描,用三色標記的算法。V8使用每個對象的兩個mark-bits和一個標記工作棧來實現標記。兩個mark-bits編碼三種顏色:白色(00),灰色(10)和黑色(11)。白色表示對象可以回收,黑色表示對象不能回收,并且他的所有引用都被便利完畢了,灰色表示不可回收,他的引用對象沒有掃描完畢。掃描過程:
  • 從已知對象開始,即roots(全局對象和激活函數), 將所有非root對象標記置為白色
  • 將root對象的所有直接引用對象入棧(marking worklist)
  • 依次pop出對象,出棧的對象標記為黑,同時將他的直接引用對象標記為灰色并push入棧
  • 棧空的時候,仍然為白色的對象可以回收
  • 回收白色的對象
  • 在清除階段,只清除沒被標記的對象。但是進行清除后,內存會出現不連續的狀態,對后續的大對象分配地址造成無意義的回收(因為可用內存的不足),這時就需要Mark-Compact來處理內存碎片了。


    Mark-Compact算法

    在對象標記死亡后,在整理的過程中,將活著的對象向另一個內存頁移動,移動完后內存頁就可以還給操作系統,但如果這一頁的活動對象被很多其他頁的對象引用,就不會compact,因為移動完后更新其他引用的指針開銷大。


    全暫停與增量標記

    垃圾回收的3種基本算法需要應用邏輯暫停下來,垃圾回收完后恢復應用程序邏輯,即“全暫停”,過長的停頓會讓用戶感到卡頓,所以為了降低全堆的垃圾回收,當堆的大小到一定程度后,開始增量GC,V8在標記階段將標記的動作分為很多小“步進”,應用邏輯與垃圾回收交替進行直到標記階段完成。但是,對于過大的堆,GC在試圖跟上應用程序分配速度的過程中,仍有長時間的停頓,并且應用程序需要通知GC對象圖的所有變化,這些都是需要成本的(寫保障 write-barrier)。V8使用Dijkstra-style 的寫屏障(write-barrier)來實現通知。當object.field = value in JavaScript時,V8會插入以下代碼:// Called after `object.field = value`. write_barrier(object, field_offset, value) {if (color(object) == black && color(value) == white) {set_color(value, grey);marking_worklist.push(value);} } 復制代碼write-barrier可以保障不會出現黑色對象指向了白色對象的現象發生(強三色不變形 strong tri-color invariant),這樣應用程序不會在GC時誤刪活動對象。在GC完成后所有白色對象都是可安全刪除的。但是,由于write-barrier的損耗,降低了應用程序的吞吐量,所以需用其他的worker threads提高吞吐量,使worker threads也可以進行標記的工作。這就是下面要講的平行標記和并發標記。


    平行標記 parallel marking

    平行標記期間,應用程序暫停,main thread和worker thread共同執行標記操作,下圖顯示了平行標記所涉及的數據結構。箭頭指示數據流的方向。其中,對象圖是只讀的,不允許去修改他,Mark-bits和Marking worklist是可以讀和寫的。Marking worklist負責決定分給其他worker thread的工作量,決定了性能與保持本地線程的均衡,所以如何高效地完成工作的分配至關重要。如下圖所示,V8使用基于內存段的方式去平衡各個線程的工作量,避免線程同步的耗時與盡可能的工作。


    并發標記 concurrent marking

    并發標記允許標記行為與應用程序同時進行。這就需要解決數據競爭的問題,比如JS代碼在更改一個對象的字段,而worker thread又在標記字段,就可能導致錯誤的垃圾回收。所以main thread需要與worker threads在發生數據競爭時進行同步,大多數的數據競爭行為通過輕量級的原子級內存訪問就可以同步,但是一些特殊的場景需要獨占整個對象的訪問。


    優化的結果

    有了平行標記與并發標記后,對比上面講的流程,GC的流程變為:
  • 從root對象開始掃描,填充對象到marking worklist
  • 分布并發標記任務到worker threads
  • worker threads幫助main thread去更快地消費marking worklist中的對象
  • main thread 偶爾會通過執行bailout worklist 和 marking worklist來marking
  • 一旦marking worklists為空,main thread 就完成GC行為
  • 在結束之前,main thread重新掃描roots,可能會發現其他的白色節點,這些白色節點會在worker threads的幫助下,被平行標記

  • 參考文獻:

    • Concurrent marking in V8
    • 解讀 V8 GC Log
    • 思維導圖:github.com/bailinlin/A…


    總結

    以上是生活随笔為你收集整理的V8 —— 你需要知道的垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。