ZGC: 可擴(kuò)展的低延遲的垃圾回收器
目標(biāo)
支持TB級(jí)堆內(nèi)存(最大4T)
最大GC停頓10ms
對(duì)吞吐量影響最大不超過(guò)15%
數(shù)據(jù)
SPECjbb 2015基準(zhǔn)測(cè)試,128G堆內(nèi)存,單次GC停頓最大1.68ms, 平均1.09ms
特性
Colored pointer
在對(duì)象的引用中借用幾個(gè)bit存儲(chǔ)額外狀態(tài)標(biāo)記,Load Barrier會(huì)根據(jù)這些狀態(tài)標(biāo)記執(zhí)行不同的邏輯
Load Barrier
加載屏障:在應(yīng)用線程從堆中加載對(duì)象應(yīng)用后,執(zhí)行的一段邏輯
跟CPU中的內(nèi)存屏障(Memory barrier)完全沒有關(guān)聯(lián)
Single generation
目前ZGC沒有分代,每次GC都會(huì)標(biāo)記整個(gè)堆
Page Allocation
將堆分為 2M(small), 32M(medium), n*2M(large)三種大小的頁(yè)面(Page)來(lái)管理,根據(jù)對(duì)象的大小來(lái)判斷在那種頁(yè)面分配
Partial compaction
在relocation階段將Page中活的對(duì)象轉(zhuǎn)移到另一個(gè)Page,并整個(gè)回收原Page。會(huì)根據(jù)一定算法選擇部分Page進(jìn)行整理。
Mostly Concurrent
大部分對(duì)象標(biāo)記和對(duì)象轉(zhuǎn)移都是可以和應(yīng)用線程并發(fā)。只會(huì)在以下階段會(huì)發(fā)生stop-the-world
1. GC開始時(shí)對(duì)root set的標(biāo)記時(shí)
2. 在標(biāo)記結(jié)束的時(shí)候,由于并發(fā)的原因,需要確認(rèn)所有對(duì)象已完成遍歷,需要進(jìn)行暫停
3. 在relocate root-set 中的對(duì)象時(shí)
原理
簡(jiǎn)述
邏輯上一次ZGC分為Mark(標(biāo)記)、Relocate(遷移)、Remap(重映射)三個(gè)階段
Mark: 所有活的對(duì)象都被記錄在對(duì)應(yīng)Page的Livemap(活對(duì)象表,bitmap實(shí)現(xiàn))中,以及對(duì)象的Reference(引用)都改成已標(biāo)記(Marked0或Marked1)狀態(tài)
Relocate: 根據(jù)頁(yè)面中活對(duì)象占用的大小選出的一組Page,將其中中的活對(duì)象都復(fù)制到新的Page, 并在額外的forward table(轉(zhuǎn)移表)中記錄對(duì)象原地址和新地址對(duì)應(yīng)關(guān)系
Remap: 所有Relocated的活對(duì)象的引用都重新指向了新的正確的地址
實(shí)現(xiàn)上,由于想要將所有引用都修正過(guò)來(lái)需要跟Mark階段一樣遍歷整個(gè)對(duì)象圖,所以這次的Remap會(huì)與下一次的Remark階段合并。
所以在GC的實(shí)現(xiàn)上是2個(gè)階段,即Mark&Remap階段和Relocate階段


向下箭頭表示STW, 橫向箭表示并發(fā)階段
Colored pointer
在64位系統(tǒng)中,ZGC利用了對(duì)象引用的4bit(低42位:對(duì)象的實(shí)際地址)

Marked0/marked1: 判斷對(duì)象是否已標(biāo)記
Remapped: 判斷應(yīng)用是否已指向新的地址
Finalizable: 判斷對(duì)象是否只能被Finalizer訪問(本文分析忽略此標(biāo)記)
這幾個(gè)bits在不同的狀態(tài)也就代表這個(gè)引用的不同顏色
為什么有2個(gè)mark標(biāo)記?
每一個(gè)GC周期開始時(shí),會(huì)交換使用的標(biāo)記位,使上次GC周期中修正的已標(biāo)記狀態(tài)失效,所有引用都變成未標(biāo)記。
GC周期1:使用mark0, 則周期結(jié)束所有引用mark標(biāo)記都會(huì)成為01。
GC周期2:使用mark1, 則期待的mark標(biāo)記10,所有引用都能被重新標(biāo)記。
內(nèi)存映射
通過(guò)Linux系統(tǒng)調(diào)用mmap將標(biāo)記位(001,010,100)三種地址空間映射到同一地址上,使三種地址解析后都指向同一地址,Load Barrer保證返回的地址是其中一個(gè)。

GC周期
GC在每個(gè)階段維護(hù)一個(gè)全局的唯一的期望標(biāo)記,當(dāng)發(fā)現(xiàn)引用的狀態(tài)跟期望的不一致,Load barrier會(huì)修復(fù)應(yīng)用的標(biāo)記到期待的狀態(tài)。并會(huì)根據(jù)狀態(tài)的不同執(zhí)行不同的邏輯。
下面分析不同階段的實(shí)現(xiàn)流程
當(dāng)前為Mark/Remap階段
期待的標(biāo)記值為001,此處只關(guān)注Mark操作,Remap邏輯下面說(shuō)明。
當(dāng)前加載的引用標(biāo)記010,Load barrier會(huì)將引用的標(biāo)記修正為001,然后保存回這個(gè)引用的來(lái)源對(duì)象中,這樣在下次再加載相同時(shí)可以避免重復(fù)執(zhí)行。同時(shí)會(huì)幫助GC進(jìn)行對(duì)象標(biāo)記,方式為將這個(gè)引用添加到當(dāng)前線程的本地標(biāo)記stack中,并發(fā)的GC線程會(huì)遍歷這些引用,并遞歸遍歷引用的對(duì)象圖
當(dāng)前為Relocation階段
期待的標(biāo)記值為100
GC線程會(huì)執(zhí)行為relocation set執(zhí)行relocate工作,將page編輯為relocating(遷移中),只遷移對(duì)象,不關(guān)注對(duì)象的引用,relocation結(jié)束后,對(duì)象的引用會(huì)指向過(guò)期的位置。
此階段業(yè)務(wù)線程加載對(duì)象引用時(shí),進(jìn)行remap操作:先判斷指向的頁(yè)面狀態(tài)是否為relocating, 如果是relocating, 會(huì)協(xié)助GC線程做relocate工作。并更新此引用的的標(biāo)記為100,如果不是relocating,直接更新標(biāo)記為100。
當(dāng)Relocation階段完成時(shí)會(huì)存在部分引用未更新,標(biāo)記為001。
來(lái)到下一次GC周期:
當(dāng)前為Mark/Remap階段
期待的標(biāo)記值為010
如果當(dāng)前加載的引用為100,表示已完成remap,更新標(biāo)記為010
如果為其他狀態(tài),則會(huì)執(zhí)行rmap操作,然后更新標(biāo)記為010
同時(shí)會(huì)對(duì)對(duì)象進(jìn)行mark操作,前面已經(jīng)說(shuō)明。
如此反復(fù)切換。
Page管理
對(duì)象分配
我們知道在一些GC算法下分配對(duì)象是通過(guò)撞指針法,也即是TLAB機(jī)制來(lái)分配。在ZGC中針對(duì)不同類型的Page,有不同的分配機(jī)制。
在堆上分配對(duì)象時(shí),是根據(jù)對(duì)象的大小選擇在不同類型的Page中分配,不同Page對(duì)象的分配策略不同。
Small Page(<=256K):每個(gè)CPU會(huì)關(guān)聯(lián)一個(gè)small page,線程在分配對(duì)象時(shí),先查找線程所運(yùn)行在的cpu id, 找到關(guān)聯(lián)的Page,進(jìn)行分配。page剩余內(nèi)存不夠時(shí),會(huì)嘗試在新Page分配并切換cpu綁定的page為新的page。
Medium Page(<=4M):?所有線程在同一個(gè)Page分配
Large Page:每個(gè)large對(duì)象占用一個(gè)Page, 根據(jù)對(duì)象大小先分配合適大小的Page,然后在Page中分配對(duì)象
ZGC觸發(fā)時(shí)機(jī)
ZGC目前有4中機(jī)制觸發(fā)GC
1. 定時(shí)觸發(fā),默認(rèn)為不使用,可通過(guò)ZCollectionInterval參數(shù)配置
2. 預(yù)熱觸發(fā),最多三次,在堆內(nèi)存達(dá)到10%、20%、30%時(shí)觸發(fā),主要時(shí)統(tǒng)計(jì)GC時(shí)間,為其他GC機(jī)制使用
3. 分配速率,基于正態(tài)分布統(tǒng)計(jì),計(jì)算內(nèi)存99.9%可能的最大分配速率,以及此速率下內(nèi)存將要耗盡的時(shí)間點(diǎn),在耗盡之前觸發(fā)GC(耗盡時(shí)間 - 一次GC最大持續(xù)時(shí)間 - 一次GC檢測(cè)周期時(shí)間)
4. 主動(dòng)觸發(fā),(默認(rèn)開啟,可通過(guò)ZProactive參數(shù)配置) 距上次GC堆內(nèi)存增長(zhǎng)10%,或超過(guò)5分鐘時(shí),對(duì)比距上次GC的間隔時(shí)間跟(49 * 一次GC的最大持續(xù)時(shí)間),超過(guò)則觸發(fā)
簡(jiǎn)單的GC示例

第一次STW, 標(biāo)記roots對(duì)象

并發(fā)標(biāo)記階段,所有活對(duì)象以及對(duì)象引用都被標(biāo)記
此后會(huì)有第二次STW,確保所有對(duì)象都被標(biāo)記

選擇需要整理的Page集合(relocation set)

第三次STW, 轉(zhuǎn)移root中的對(duì)象

當(dāng)一個(gè)Page內(nèi)的活對(duì)象全部轉(zhuǎn)移后,此Page的內(nèi)存可以立即重用。
這是個(gè)和有用的特性,relocation set中下個(gè)page的對(duì)象可以轉(zhuǎn)移到這個(gè)釋放的內(nèi)存中,理論上在GC時(shí)只需要有一個(gè)可轉(zhuǎn)移的空頁(yè)就可以了。

到此,一個(gè)GC周期就結(jié)束了。


剩下的修復(fù)工作由Load Barrier以及下次GC來(lái)完成
轉(zhuǎn)載請(qǐng)注明來(lái)源:http://www.itdecent.cn/p/4e4fd0dd5d25
參考
https://www.zhihu.com/question/287945354/answer/458761494
http://dinfuehr.github.io/blog/a-first-look-into-zgc/
http://cr.openjdk.java.net/~pliden/slides/ZGC-Jfokus-2018.pdf
https://www.usenix.org/legacy/events/vee05/full_papers/p46-click.pdf
http://go.azul.com/continuously-concurrent-compacting-collector