理解JVM垃圾收集器ZGC

一、來(lái)源

  • ZGC收集器是由Oracle公司研發(fā)的。2018年創(chuàng)建了JEP 333將ZGC提交給OpenJDK,推動(dòng)其進(jìn)入OpenJDK11的發(fā)布清單中。

二、ZGC的堆內(nèi)存布局

  • 與Shenandoah和G1一樣,ZGC也采用基于Region的堆內(nèi)存布局。
  • ZGC的Region具有動(dòng)態(tài)性。
    • 動(dòng)態(tài)創(chuàng)建和銷毀
    • 動(dòng)態(tài)的區(qū)域容量大小

分類如下:

  • 小型Region(Small Region):容量固定為2MB,用于放置小于256KB的小對(duì)象。
  • 中型Region(Medium Region):容量固定為32MB,用于放置大于等于256KB但小于4MB的對(duì)象。
  • 大型Region(Large Region):容量不固定,可以動(dòng)態(tài)變化,但必須為2MB的整數(shù)倍,用于放置4MB或以上的大對(duì)象。每個(gè)大型Region中只會(huì)存放一個(gè)大對(duì)象,所以實(shí)際容量可能小于中型Region,最小容量可低至4MB。大型Region在ZGC的實(shí)現(xiàn)中是不會(huì)被重分配的,因?yàn)閺?fù)制一個(gè)大對(duì)象的代價(jià)非常高昂。

三、并發(fā)整理算法的實(shí)現(xiàn)。

3.1 算法的由來(lái)

  • G1收集器的篩選回收階段是stop the world的,但收集器線程間是并行的,之所以不和用戶線程并發(fā)執(zhí)行,是因?yàn)镚1只回收一部分Region,停頓時(shí)間是用戶可以控制的。所以并不著急去實(shí)現(xiàn),交給了ZGC去實(shí)現(xiàn)。
  • 并且因?yàn)镚1為了不影響吞吐量才選擇stw的。停頓用戶線程可以最大幅度提高垃圾收集效率。

3.2 實(shí)現(xiàn)

3.2.1 讀屏障

指針的自愈能力

  • 在ZGC中,當(dāng)讀取處于重分配集的對(duì)象時(shí),會(huì)被讀屏障攔截,通過(guò)轉(zhuǎn)發(fā)表記錄將訪問(wèn)轉(zhuǎn)發(fā)到新復(fù)制的對(duì)象上,并同時(shí)修正更新該引用的值,使其直接指向新對(duì)象。ZGC將這種行為叫做指針的“自愈能力”。
  • 好處是:第一次訪問(wèn)舊對(duì)象訪問(wèn)會(huì)變慢,但也只會(huì)有一次變慢,當(dāng)“自愈”完成后,后續(xù)訪問(wèn)就不會(huì)變慢了。
    • Shenandoah每次訪問(wèn)都慢,對(duì)比發(fā)現(xiàn),ZGC的執(zhí)行負(fù)載更低。

3.2.2 染色指針技術(shù)

3.2.2.1 HotSpot虛擬機(jī)的標(biāo)記實(shí)現(xiàn)方案有如下幾種:

  • 把標(biāo)記直接記錄在對(duì)象頭上(如Serial收集器);
  • 把標(biāo)記記錄在與對(duì)象相互獨(dú)立的數(shù)據(jù)結(jié)構(gòu)上(如G1、Shenandoah使用了一種相當(dāng)于堆內(nèi)存的1/64大小的,稱為BitMap的結(jié)構(gòu)來(lái)記錄標(biāo)記信息);
  • 直接把標(biāo)記信息記在引用對(duì)象的指針上(如ZGC)

為什么會(huì)放在指針上呢?

  • 追蹤式收集算法的標(biāo)記階段就是看有沒有引用,所以可以只和指針打交道而不管指針?biāo)玫膶?duì)象本身。
  • 例如對(duì)象標(biāo)記過(guò)程就是打個(gè)三色標(biāo)記,這些標(biāo)記本質(zhì)上只和對(duì)象引用有關(guān),和對(duì)象本身無(wú)關(guān)。某個(gè)對(duì)象只有它的引用關(guān)系才能決定它的存活。

3.2.2.2 染色指針的解釋

  • 染色指針是一種直接將少量額外的信息存儲(chǔ)在指針上的技術(shù)。目前在Linux下64位的操作系統(tǒng)中高18位是不能用來(lái)尋址的,但是剩余的46為卻可以支持64T的空間,到目前為止我們幾乎還用不到這么多內(nèi)存。于是ZGC將46位中的高4位取出,用來(lái)存儲(chǔ)4個(gè)標(biāo)志位,剩余的42位可以支持4TB(2的42次冪)的內(nèi)存,也直接導(dǎo)致ZGC可以管理的內(nèi)存不超過(guò)4TB,如圖所示:
    • 限制:只能在64位系統(tǒng)上,因?yàn)閆GC設(shè)置就是用的42-46位,32位明顯不夠嘛。。并且不支持壓縮指針(這一塊可以參考Java對(duì)象模型中的OOP,meta中有一個(gè)Klass直接指向Klass,還一個(gè)壓縮指針)如下。
union _metadata {
    之前都是oop,現(xiàn)在直接指向Klass了
    Klass*      _klass;
    narrowKlass _compressed_klass;
} _metadata;
染色指針.png

3.2.2.3 染色指針的設(shè)計(jì)

  • ZGC使用了內(nèi)存多重映射(Multi-Mapping)將多個(gè)不同的虛擬內(nèi)存地址映射到同一個(gè)物理內(nèi)存地址上,這是一種多對(duì)一映射。

  • 因?yàn)槿旧羔樦皇侵匦露x內(nèi)存中某些指針的其中幾位,OS又不支持,OS只會(huì)把整個(gè)指針當(dāng)做一個(gè)內(nèi)存地址來(lái)對(duì)待,只是它自己瞎想,為了解決這個(gè)問(wèn)題,使用了現(xiàn)代處理器的虛擬內(nèi)存映射技術(shù)

  • 現(xiàn)代處理器一般使用請(qǐng)求分頁(yè)機(jī)制+虛擬內(nèi)存映射技術(shù)。

    • 請(qǐng)求分頁(yè)機(jī)制把線性地址空間和物理地址空間分別劃分為大小相等的塊。這樣的塊稱為頁(yè)。通過(guò)在線性虛擬空間的頁(yè)和物理地址空間的頁(yè)建立映射表,分頁(yè)機(jī)制會(huì)進(jìn)行線性地址到物理地址的映射,完成線性地址到物理地址的轉(zhuǎn)換。
    • Linus/x86-64平臺(tái)上的ZGC使用了多重映射將多個(gè)不同的虛擬內(nèi)存地址映射到同一個(gè)物理內(nèi)存地址上,多對(duì)一映射。意味著ZGC在虛擬內(nèi)存空間中看到的地址空間比實(shí)際的堆內(nèi)存容量更大。
  • 把染色指針中的標(biāo)志位看做是地址的分段符,只要把這些不同的地址段映射到同一個(gè)物理地址空間就行了,經(jīng)過(guò)多重映射轉(zhuǎn)換后,就可以使用染色指針正常進(jìn)行尋址了。

    • 標(biāo)志位就是上圖的Remapped,Marked1,Marked0。
ZGC多重映射下的尋址.jpg

3.2.2.4 染色指針的作用。

  • 一旦某個(gè)Region的存活對(duì)象被移走之后,這個(gè)Region立即就能夠被釋放和重用掉,而不必等待整個(gè)堆中所有指向該Region的引用都被修正后才能清理,這使得理論上只要還有一個(gè)空閑Region,ZGC就能完成收集。而Shenandoah需要等到更新階段結(jié)束才能釋放回收集中的Region,如果Region里面對(duì)象都存活的時(shí)候,需要1:1的空間才能完成收集。

  • 染色指針可以大幅減少在垃圾收集過(guò)程中內(nèi)存屏障的使用數(shù)量,ZGC只使用了讀屏障。因?yàn)樾畔⒅苯泳S護(hù)在指針中。

  • 染色指針具備強(qiáng)大的擴(kuò)展性,它可以作為一種可擴(kuò)展的存儲(chǔ)結(jié)構(gòu)用來(lái)記錄更多與對(duì)象標(biāo)記、重定位過(guò)程相關(guān)的數(shù)據(jù),以便日后進(jìn)一步提高性能。

四、ZGC的過(guò)程

ZGC運(yùn)作.jpg
  • 并發(fā)標(biāo)記(Concurrent Mark):與G1、Shenandoah一樣,并發(fā)標(biāo)記是遍歷對(duì)象圖做可達(dá)性分析的階段,它的初始標(biāo)記和最終標(biāo)記也會(huì)出現(xiàn)短暫的停頓,整個(gè)標(biāo)記階段只會(huì)更新染色指針中的Marked 0、Marked 1標(biāo)志位。

  • 并發(fā)預(yù)備重分配(Concurrent Prepare for Relocate):這個(gè)階段需要根據(jù)特定的查詢條件統(tǒng)計(jì)得出本次收集過(guò)程要清理哪些Region,將這些Region組成重分配集(Relocation Set)。ZGC每次回收都會(huì)掃描所有的Region,用范圍更大的掃描成本換取省去G1中記憶集的維護(hù)成本。

    • ZGC的重分配集只是決定里面的存活對(duì)象會(huì)被復(fù)制到其他的Region。不是為了效益回收
    • JDK12的ZGC中開始支持的類卸載以及弱引用的處理,也是在這個(gè)階段完成的。
  • 并發(fā)重分配(Concurrent Relocate):重分配是ZGC執(zhí)行過(guò)程中的核心階段,這個(gè)過(guò)程要把重分配集中的存活對(duì)象復(fù)制到新的Region上,并為重分配集中的每個(gè)Region維護(hù)一個(gè)轉(zhuǎn)發(fā)表(Forward Table),記錄從舊對(duì)象到新對(duì)象的轉(zhuǎn)向關(guān)系。

    • ZGC收集器能僅從引用上就明確得知一個(gè)對(duì)象是否處于重分配集之中,如果用戶線程此時(shí)并發(fā)訪問(wèn)了位于重分配集中的對(duì)象,這次訪問(wèn)將會(huì)被預(yù)置的內(nèi)存屏障所截獲,然后立即根據(jù)Region上的轉(zhuǎn)發(fā)表記錄將訪問(wèn)轉(zhuǎn)發(fā)到新復(fù)制的對(duì)象上,并同時(shí)修正更新該引用的值,使其直接指向新對(duì)象,ZGC將這種行為稱為指針的“自愈”(Self-Healing)能力。
    • ZGC的染色指針因?yàn)椤白杂保⊿elf-Healing)能力,所以只有第一次訪問(wèn)舊對(duì)象會(huì)變慢,而Shenandoah的Brooks轉(zhuǎn)發(fā)指針是每次都會(huì)變慢。 一旦重分配集中某個(gè)Region的存活對(duì)象都復(fù)制完畢后,這個(gè)Region就可以立即釋放用于新對(duì)象的分配,但是轉(zhuǎn)發(fā)表還得留著不能釋放掉,因?yàn)榭赡苓€有訪問(wèn)在使用這個(gè)轉(zhuǎn)發(fā)表。
  • 并發(fā)重映射(Concurrent Remap):重映射所做的就是修正整個(gè)堆中指向重分配集中舊對(duì)象的所有引用,但是ZGC中對(duì)象引用存在“自愈”功能,所以這個(gè)重映射操作并不是很迫切。ZGC很巧妙地把并發(fā)重映射階段要做的工作,合并到了下一次垃圾收集循環(huán)中的并發(fā)標(biāo)記階段里去完成,反正它們都是要遍歷所有對(duì)象的,這樣合并就節(jié)省了一次遍歷對(duì)象圖的開銷。

五、ZGC的優(yōu)點(diǎn)

  • 低停頓,高吞吐量,ZGC收集過(guò)程中額外耗費(fèi)的內(nèi)存小
  • G1通過(guò)寫屏障維護(hù)記憶集,才能處理跨代指針,得以實(shí)現(xiàn)增量回收。記憶集占用大量?jī)?nèi)存,寫屏障對(duì)正常程序造成額外負(fù)擔(dān)。
  • ZGC沒有寫屏障,卡表之類的。
  • 在多核處理器的某種架構(gòu)下,ZGC優(yōu)先在線程當(dāng)前所處的處理器的本地內(nèi)存上分配對(duì)象,以保證內(nèi)存高效訪問(wèn)。

六、ZGC的缺點(diǎn)

  1. 承受的對(duì)象分配速率不會(huì)太高,因?yàn)楦?dòng)垃圾。
  • ZGC的停頓時(shí)間是在10ms以下,但是ZGC的執(zhí)行時(shí)間還是遠(yuǎn)遠(yuǎn)大于這個(gè)時(shí)間的。假如ZGC全過(guò)程需要執(zhí)行10分鐘,在這個(gè)期間由于對(duì)象分配速率很高,將創(chuàng)建大量的新對(duì)象,這些對(duì)象很難進(jìn)入當(dāng)次GC,所以只能在下次GC的時(shí)候進(jìn)行回收,這些只能等到下次GC才能回收的對(duì)象就是浮動(dòng)垃圾。
  • 造成回收到的內(nèi)存空間小于期間并發(fā)產(chǎn)生的浮動(dòng)垃圾所占的空間。

ZGC沒有分代概念,每次都需要進(jìn)行全堆掃描,導(dǎo)致一些“朝生夕死”的對(duì)象沒能及時(shí)的被回收。

6.1 解決辦法

  1. 增加堆容量大小,使得程序得到更多的喘息時(shí)間。治標(biāo)不治本的方案。
  2. 從根本上解決這個(gè)問(wèn)題,還是需要引入分代收集。讓新生對(duì)象在一個(gè)專門區(qū)域創(chuàng)建,然后專門針對(duì)這個(gè)區(qū)域進(jìn)行更頻繁的,更快的收集。

參考資料

《深入理解Java虛擬機(jī)第三版》

最后編輯于
?著作權(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)容