ZGC原理與實(shí)現(xiàn)分析

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

http://www.itdecent.cn/p/5f9095ccc11e

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容