簡介
-
Shenandoah GC 與 ZGC 同為新一代的低延遲收集器, 分別由RedHat和Oracle開發, 目前還在實驗階段, 尚未使用於生產環境。
-
GC的三項指標: Footprint(內存占用), Throughput(吞吐量) 與 Latency(延遲), 有點像CAP理論, 三者只能取其二。
-
目前主流GC是G1, 而此兩者的延時比G1低很多。
Shenandoah GC
-
操作系統支持:
- Linux, Windows, macOS, Solaris
-
硬件支持:
-
x86_64
-
x86_32
-
AArch64
-
-
JDK支持(注意是OpenJDK的非Oracle Build, 因為Oracle不想為RedHat寫的代碼負責任)
- JDK8, JDK11, JDK13
-
Shenandoah 與 G1
-
Shenandoah 可以說是G1的下一代繼承者
- 它們擁有相似的堆內存布局
- 在初始標記, 並發標記等多階段的處理思路上都高度一致, 甚至共享了一部分實現代碼
-
Shenandoah 相比 G1的改進:
-
堆內存管理方面:
-
支持並發的整理算法, 能與用戶線程並發
-
默認不使用分代收集
-
摒棄了在G1中耗費大量內存和計算資源去維護的記憶集(Memory Set), 改用名為連接矩陣(Connection Matrix) 的全局數據結構來記錄跨Region的引用關系。從而降低了處理跨帶指針時的記憶集維護消耗以及偽共享問題的發生概率。
-
連接矩陣示意圖(其實這張圖好像錯了, 第一行的X應該在[3, 1]的位置)
- 說白了這就是個臨接矩陣, 橫坐標代表當前區域, 縱坐標代表當前區域引用的區域
- 那么就是區域5引用了區域3, 而區域3又引用了區域1
-
-
-
-
-
Shenandoah GC的工作過程大致可划分為9個階段
-
Initial Marking(初始標記)
- 與G1一致, 首先標記與GC Roots直接關聯的對象, 此階段仍會"Stop The World", 但停頓時間與堆大小無關, 只與GC Roots的數量相關。
-
Concurrent Marking(並發標記)
- 與G1一致, 遍歷對象圖, 標記出全部可達的對象, 此階段是與用戶線程一起並發的, 時間長短取決於堆中存活對象的數量以及圖的結構復雜程度。
-
Final Marking(最終標記)
-
與G1一致, 處理剩余的SATB掃描, 並在此階段統計出回收價值最高的Region, 將這些Region構成一組回收集(Collection Set)。
-
最終標記階段也會有一小段短暫的停頓。
-
-
Concurrent Cleanup(並發清理)
- 此階段用於清理那些整個區域內連一個存活對象都沒有找到的Region(Immediate Garbage Region)
-
Concurrent Evacuation(並發回收)
-
在此階段, Shenandoah要把回收集里面的存活對象先復制一份到其他未被使用的Region中。
-
重點是復制對象過程不凍結用戶線程而是與用戶線程並行, 這一實現有很大的技術屏障, Shenandoah 通過讀屏障和 Brooks Pointers(轉發指針) 解決了此困難。
-
並發回收階段運行時間長短取決於回收集的大小。
-
-
Initial Update Reference(初始引用更新)
-
並發回首階段復制對象結束后, 還需要把堆中所有指向舊對象的引用修正到復制后的新地址, 此操作稱為引用更新。
-
實際上此階段並沒有做什么實際的處理, 只是為了建立一個線程集合點, 確保所有並發回收階段中進行的收集器線程都已完成分配給它們的對象移動任務而已。
-
初始引用更新時間很短, 會產生一個非常短暫的停頓。
-
-
Concurrent Update Reference(並發引用更新)
-
真正開始引用更新操作, 此階段是與用戶線程並發的, 時間長短取決於內存中涉及的引用數量的多少。
-
與並發標記不同, 不需要再沿着對象圖來搜索, 只需要按照內存物理地址的順序, 線性地搜索出引用類型, 把舊值改為新值即可。
-
-
Final Update Reference(最終引用更新)
-
處理了堆中的引用更新后, 還需要修正存於GC Roots中的引用。
-
此階段是Shenandoah的最后一次停頓, 停頓事件只與GC Roots數量相關。
-
-
Concurrent Cleanup(並發清理)
-
經過並發回收和引用更新之后, 整個回收集中所有的Region已再無存活對象, 這些Region都變成了Immediate Garbage Regions了。
-
最后再調用一次並發清理過程來回收這些Region的內存空間, 供以后新對象分配使用。
-
-
-
工作過程示意圖
-
白色: 自由空間
-
藍色: 新分配的空間
-
綠色: 存活的對象
-
黃綠白: 被選入回收集的存活對象
-
黃黃白: 需要更新引用的被選入回收集的存活對象
-
ZGC
-
可以說是Oracle抄了 Azul System的作業, ZGC 與 Azul System公司的PGC(Pauseless GC) 和 C4(Concurrent Continuously Compacting Collector) 在算法和實現原理上是高度相似的, 只存在術語稱謂的區別。
-
主要特征:
- 基於Region內存布局
- 不設分代
- 使用了讀屏障, 染色指針和內存多重映射等技術來實現可並發的標記-整理算法
- 以低延遲為首要目標
-
ZGC也采用基於Region的堆內存布局, 但與它們不同的是, ZGC的Region具有動態性: 動態創建和銷毀, 以及動態的區域容量大小。在x64硬件平台下, ZGC的Region可以有以下三類容量:
-
Small Region
- 容量固定為2MB, 用於放置小於256KB的小對象。
-
Medium Region
- 容量固定為32MB, 用於放置大於等於256KB但小於4MB的對象。
-
Large Region
- 容量不固定, 可以動態變化, 但必須為2MB的整數倍, 用於放置4MB或以上的大對象。
- 每個大型Region中只會存放一個大對象, 它雖名為Large Region, 但它的實際容量完全有可能小於中型Region。
- 大型Region在ZGC的實現中是不會被重分配的, 因為復制一個大對象的代價非常高昂。
-
-
染色指針技術
-
在64位系統中, 理論可以訪問的內存高達16EB。實際上基於需求, 性能, 和成本考慮, 在AMD64架構中只支持到52位(4PB)的地址總線和48位(256TB)的虛擬地址空間, 目前64位的硬件實際能夠支持的最大內存只有256TB。此外操作系統還有自己的約束, 64Linux系統分別支持47位(128TB)的進程虛擬地址和46位(64TB)的物理地址空間, 64位的Windows系統只支持44位(16TB)的物理地址空間。
-
雖然Linux下64位指針的高18位不能用來尋址, 剩余的46位指針所能支持的64TB內存在今天仍能夠充分滿足大型服務器需要。而ZGC則利用了剩下的46位指針的高4位提取出來用於存儲四個標志信息。
- 通過這些標志位, 虛擬機可以直接從指針中看到其引用對象的狀態, 是否進入重分配集, 是否通過finalize()方法才能被訪問到。
- 由於進一步壓縮了原本只有46位的地址空間, ZGC能夠管理的內存不可以超過4TB。
-
三大優勢
- 染色指針可以使得一旦某個Region的存活對象被移走之后, 此Region立即就能夠被釋放和重用掉, 而不必等待整個堆中所有指向該Region的引用都被修正后才能清理。
- 染色指針可以大幅減少在垃圾收集過程中內存屏障的使用數量。
- 染色指針可以作為一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據, 以便日后進一步提高性能。
-
在Linux/X86-64平台上的ZGC使用了多重映射(Multi-Mapping) 將多個不同的虛擬內存映射到同一個物理內存地址上。
- 任何的進程在進程自己看來自己的內存空間都是連續的, 但是計算機實際的物理內存並不是與該進程的內存是一一對應的。碎片化的物理內存可以映射成一個完整的虛擬內存, 同時應用可以申請比物理內存大的內存, 使得多個內存互不干擾, 使編譯好的二進制文件的地址統一化......
-
ZGC運作過程
-
Concurrent Mark(並發標記)
-
遍歷對象圖做可達性分析的階段, 前后也要經過類似於G1, Shenandoah 的初始標記, 最終標記的短暫停頓。
-
與G1, Shenandoah不同的是, ZGC的標記是在指針上而不是在對象上進行的, 標記階段會更新染色指針中的Marked0、Marked1標志位。
-
-
Concurrent Prepare for Relocate(並發預備重分配)
- 此階段需要根據特定的查詢條件統計出本次收集過程要清理哪些Region, 將這些Region組成重分配集(Relocation Set)。
-
Concurrent Relocate(並發重分配)
-
是ZGC執行過程中的核心階段, 此過程要把重分配集中的存活對象復制到新的Region上, 並為重分配集中的每個Region維護一個轉發表(Forward Table), 記錄從舊對象到新對象的轉向關系。
-
由於染色指針的存在, ZGC能僅從引用上就明確得知一個對象是否處於重分配集之中。如果用戶線程此時並發訪問了位於重分配集中的對象, 這次訪問將會被預置的內存屏障截獲, 然后立即根據Region上的轉發表記錄將訪問轉發到新復制的對象上, 並同時修正該引用的值, 使其直接指向新對象, 此即為Self-Healing(自愈)[只有第一次訪問舊對象會陷入轉發]。
-
-
Concurrent Remap(並發重映射)
- 修正整個堆中指向重分配集中舊對象的所有引用。
- 重映射清理這些舊引用的主要目的是為了不變慢, 並不是很迫切。
- ZGC將並發重映射階段要做的工作, 合並到了下一次垃圾收集循環中的並發標記階段里去完成, 從而節省了一次遍歷對象圖的開銷。
-
-