Oracle即将发布的全新Java垃圾收集器 ZGC
Java 11的特性集合已經(jīng)確定,其中包含了一些非常棒的特性。新版本提供了一個(gè)全新的垃圾回收器ZGC,它由甲骨文開(kāi)發(fā),承諾在TB級(jí)別的堆上實(shí)現(xiàn)非常低的停頓時(shí)間。在本文中,我們將介紹甲骨文開(kāi)發(fā)ZGC的動(dòng)機(jī)、ZGC的技術(shù)概覽以及ZGC帶來(lái)的一些非常令人興奮的可能性。
\\那么為什么要開(kāi)發(fā)ZGC?畢竟Java 10中已經(jīng)帶有4款久經(jīng)考驗(yàn)的垃圾回收器。Hotspot最新的垃圾回收器G1是在2006年推出的。當(dāng)時(shí)最大的AWS實(shí)例是m1.small,配備1個(gè)vCPU和1.7GB內(nèi)存,而到了今天,AWS提供了x1e.32xlarge實(shí)例,配備了128個(gè)vCPU和令人難以置信的3,904GB內(nèi)存。ZGC所針對(duì)的是這些在未來(lái)普遍存在的大容量?jī)?nèi)存:TB級(jí)別的堆容量,具有很低的停頓時(shí)間(小于10毫秒),對(duì)整體應(yīng)用性能的影響也很小(對(duì)吞吐量的影響低于15%)。ZGC所采用的機(jī)制也可以在未來(lái)進(jìn)行擴(kuò)展,以支持一些令人興奮的特性,如多層堆(用于熱對(duì)象的DRAM和用于低頻訪問(wèn)對(duì)象的NVMe閃存)或壓縮堆。
\\GC術(shù)語(yǔ)
\\要了解ZGC在現(xiàn)有垃圾回收器中所處的位置,以及它是如何達(dá)到這個(gè)位置的,我們先需要先了解一些術(shù)語(yǔ)。最基本的GC包括識(shí)別出不再使用的內(nèi)存,并將其變?yōu)榭捎玫摹,F(xiàn)代垃圾回收器通常分幾個(gè)階段來(lái)完成回收過(guò)程,如下所示:
\\- 并行(Parallel)——運(yùn)行中的JVM包含應(yīng)用程序線程和GC線程。在并行階段,會(huì)運(yùn)行多個(gè)GC線程,也就是說(shuō)任務(wù)被拆分給它們?nèi)ネ瓿?。至于GC線程是否可以與正在運(yùn)行的應(yīng)用程序線程重疊,這個(gè)在規(guī)范中并沒(méi)有特別說(shuō)明。\\t
- 串行(Serial)——串行階段只有單個(gè)GC線程在運(yùn)行。與上面的并行階段一樣,規(guī)范中也沒(méi)有說(shuō)明GC線程是否可以與當(dāng)前運(yùn)行的應(yīng)用程序線程重疊。\\t
- Stop The World(STW)——在這個(gè)階段,應(yīng)用程序線程被暫停,讓GC線程執(zhí)行它們的任務(wù)。當(dāng)你遇到GC停頓時(shí),說(shuō)明虛擬機(jī)進(jìn)入了STW階段。\\t
- 并發(fā)(Concurrent)——在并發(fā)階段,GC線程可以在運(yùn)行應(yīng)用程序線程的同時(shí)執(zhí)行自己的任務(wù)。并發(fā)階段非常復(fù)雜,因?yàn)閼?yīng)用程序線程有可能在GC完成之前將其中斷。\\t
- 增量(Incremental)——在增量階段,它可以運(yùn)行一段時(shí)間,并基于某些條件提前終止,例如時(shí)間預(yù)算或執(zhí)行更高優(yōu)先級(jí)的GC階段。\
權(quán)衡取舍
\\需要指出的是,所有這些屬性都存在權(quán)衡。例如,并行階段將利用多個(gè)GC線程來(lái)執(zhí)行任務(wù),但這樣做會(huì)導(dǎo)致協(xié)調(diào)線程的開(kāi)銷。同樣,并發(fā)階段不會(huì)暫停應(yīng)用程序線程,但可能涉及更多的開(kāi)銷和復(fù)雜性。
\\ZGC
\\在了解了GC不同階段的屬性后,現(xiàn)在讓我們來(lái)探討ZGC的工作原理。ZGC使用了兩項(xiàng)新技術(shù):彩色指針和加載屏障。
\\指針著色
\\指針著色是將信息存儲(chǔ)在指針(或引用)中的一種技術(shù)。這是有可能的,因?yàn)樵?4位平臺(tái)上(ZGC僅支持64位),指針可以處理比系統(tǒng)實(shí)際擁有的內(nèi)存更大的內(nèi)存,因此可以使用多余的位來(lái)存儲(chǔ)狀態(tài)。ZGC將堆限制為4TB,需要42位,剩下的22位當(dāng)中目前已經(jīng)使用了4位:finalizable、remap、mark0和mark1。
\\不過(guò),指針著色也存在一個(gè)問(wèn)題,當(dāng)你想要取消引用指針時(shí),需要做額外的工作,因?yàn)槟阈枰帘蔚粜畔⑽弧PARC平臺(tái)已經(jīng)為指針屏蔽提供了內(nèi)置硬件支持,所以這不是什么問(wèn)題。但x86平臺(tái)還沒(méi)有提供類似的支持,所以ZGC團(tuán)隊(duì)針對(duì)x86平臺(tái)使用了多次映射技術(shù)。
\\多次映射
\\要了解多映射的工作原理,我們需要先簡(jiǎn)要地解釋一下虛擬內(nèi)存和物理內(nèi)存之間的區(qū)別。物理內(nèi)存是系統(tǒng)可用的實(shí)際內(nèi)存,也就是DRAM芯片的容量。虛擬內(nèi)存是抽象的,對(duì)于應(yīng)用程序來(lái)說(shuō),它們有自己的物理內(nèi)存試圖(通常是隔離的)。操作系統(tǒng)負(fù)責(zé)維護(hù)虛擬內(nèi)存和物理內(nèi)存之間的映射,通過(guò)使用頁(yè)表和處理器的內(nèi)存管理單元(MMU)以及轉(zhuǎn)換后備緩沖區(qū)(TLB,用于轉(zhuǎn)換應(yīng)用程序的請(qǐng)求地址)來(lái)實(shí)現(xiàn)。
\\多次映射技術(shù)將不同范圍的虛擬內(nèi)存映射到同一物理內(nèi)存上。在remap、mark0和mark1當(dāng)中,同一時(shí)間點(diǎn)只能有一個(gè)為1,因此可以使用三個(gè)映射。ZGC源代碼中提供了一個(gè)很直觀的圖表(http://hg.openjdk.java.net/zgc/zgc/file/59c07aef65ac/src/hotspot/os_cpu/linux_x86/zGlobals_linux_x86.hpp#l39)。
\\加載屏障
\\加載屏障是一小段代碼,當(dāng)應(yīng)用程序線程從堆加載引用時(shí)就會(huì)運(yùn)行這段代碼(即訪問(wèn)對(duì)象的非原始類型字段):
\\\void printName( Person person ) {\ String name = person.name; // 將會(huì)觸發(fā)加載屏障,因?yàn)閺亩阎屑虞d了一個(gè)引用\ System.out.println(name); // 沒(méi)有直接使用加載屏障\}\\第一行代碼是給變量name賦值,這需要跟蹤堆上的person引用,然后再加載name引用。這個(gè)時(shí)候會(huì)觸發(fā)加載屏障。第二行代碼在屏幕上打印name,不會(huì)直接觸發(fā)加載屏障,因?yàn)椴恍枰虞d堆引用——name是局部變量,因此不需要從堆加載引用。不過(guò),System和out,或者println內(nèi)部可能會(huì)觸發(fā)其他加載屏障。
\\這與其他垃圾回收器(例如G1)使用的寫(xiě)入屏障形成對(duì)比。加載屏障的任務(wù)是檢查引用的狀態(tài),并在將引用(或者不同的引用)返回給應(yīng)用程序之前執(zhí)行一些任務(wù)。在ZGC中,它會(huì)對(duì)加載的引用進(jìn)行測(cè)試,查看是否設(shè)置了某些位,具體取決于當(dāng)前處于哪個(gè)階段。如果引用通過(guò)測(cè)試,就不執(zhí)行任何其他操作,如果沒(méi)有通過(guò),就會(huì)在將引用返回給應(yīng)用程序之前執(zhí)行一些特定于當(dāng)前階段的操作。
\\標(biāo)記
\\在了解了這兩項(xiàng)新技術(shù)后,現(xiàn)在讓我們來(lái)看看ZGC的GC周期。GC周期的第一部分是標(biāo)記,就是以某種方式查找并標(biāo)記應(yīng)用程序可以訪問(wèn)到的所有堆對(duì)象,換句話說(shuō),就是查找非垃圾對(duì)象。
\\ZGC的標(biāo)記分為三個(gè)階段。第一階段是STW,在這一階段,GC root被標(biāo)記為存活。GC root類似于局部變量,應(yīng)用程序使用它們來(lái)訪問(wèn)堆上的其他對(duì)象。從GC root開(kāi)始遍歷對(duì)象圖,如果某些對(duì)象無(wú)法被訪問(wèn)到,那么應(yīng)用程序也就無(wú)法訪問(wèn)到這些對(duì)象,它們就被認(rèn)為是垃圾。可以從GC root訪問(wèn)到的對(duì)象集被稱為存活集。GC root標(biāo)記步驟所需要的時(shí)間非常短,因?yàn)镚C root的總量通常相對(duì)較少。
\\\\標(biāo)記階段完成后,應(yīng)用程序恢復(fù)運(yùn)行,而ZGC將開(kāi)始下一階段,發(fā)遍歷對(duì)象圖,并標(biāo)記所有可訪問(wèn)的對(duì)象。在這一階段,加載屏障會(huì)檢查所有已加載的引用,看看它們的掩碼是否已經(jīng)針對(duì)這一階段進(jìn)行過(guò)標(biāo)記,如果尚未標(biāo)記,就將其添加到待標(biāo)記隊(duì)列。
\\在完成這一步后,會(huì)出現(xiàn)一個(gè)短暫的STW階段,它會(huì)處理一些邊緣情況,然后整個(gè)標(biāo)記過(guò)程就完成了。
\\重定位
\\GC周期的下一個(gè)主要部分是重定位。重定位就是要移動(dòng)存活對(duì)象,以便釋放部分堆空間。為什么要移動(dòng)對(duì)象而不是填補(bǔ)空隙?有些GC確實(shí)是這樣做的,但這樣會(huì)造成不好的后果,即堆分配將變得非常昂貴,因?yàn)樵诜峙涠芽臻g時(shí),分配器需要找到放置對(duì)象的空閑空間。相反,如果可以釋放大塊內(nèi)存,堆空間分配就會(huì)變得很簡(jiǎn)單,只需要將指針按照對(duì)象所需的內(nèi)存量進(jìn)行遞增就可以了。
\\ZGC將堆分成頁(yè),在開(kāi)始進(jìn)行重定位時(shí),它會(huì)選擇一組需要重新定位的存活對(duì)象的頁(yè)。在選擇好重定位集后,會(huì)出現(xiàn)一次STW停頓,ZGC對(duì)重定位集中的對(duì)象進(jìn)行重定位,并重新映射它們對(duì)新地址的引用。與之前的STW一樣,停頓時(shí)間取決于root的數(shù)量以及重定位集與存活集的比率,這個(gè)比率通常都很小。它不會(huì)隨著堆大小的變化而變化,這與其他大部分垃圾回收器一樣。
\\移動(dòng)完root之后,下一階段是進(jìn)行并發(fā)重定位。在這個(gè)階段,GC線程遍歷重定位集,并重新定位頁(yè)中的所有對(duì)象。如果應(yīng)用程序線程嘗試加載重定位集中的對(duì)象,但這些對(duì)象還未被重定位,那么應(yīng)用程序線程也可以對(duì)它們進(jìn)行重定位,這是通過(guò)加載屏障來(lái)實(shí)現(xiàn)的,如下面的流程圖所示:
\\\\這樣可以確保應(yīng)用程序看到的所有引用都是最新的,并且應(yīng)用程序不會(huì)對(duì)正在被重定位的對(duì)象做任何操作。
\\GC線程最終會(huì)重定位重定位集中的所有對(duì)象,不過(guò)仍然可能存在一些指向這些對(duì)象舊地址的引用。GC會(huì)遍歷對(duì)象圖,并將所有這些引用重新映射到新的地址上,但這是一個(gè)非常昂貴的步驟。所以,這一步被并入到下一個(gè)標(biāo)記階段。在標(biāo)記期間,如果發(fā)現(xiàn)未重新映射的引用,則將其重新映射,并標(biāo)記為存活。
\\回顧
\\試圖單獨(dú)理解復(fù)雜的垃圾回收器(如ZGC)性能特征是很困難的,但有一點(diǎn)是很清楚的,我們?cè)谖闹兴岬降腉C停頓都與GC root有關(guān),而與存活對(duì)象集、堆大小或垃圾對(duì)象沒(méi)有關(guān)系。標(biāo)記階段的最后一次停頓是一個(gè)例外,它是增量進(jìn)行的,而且如果超過(guò)時(shí)間預(yù)算,GC將恢復(fù)到并發(fā)標(biāo)記,直到下一次進(jìn)行嘗試。
\\性能
\\那么ZGC的性能如何?ZGC的SPECjbb 2015吞吐量數(shù)據(jù)與Parallel GC(為吞吐量進(jìn)行過(guò)優(yōu)化)大致相當(dāng),平均停頓時(shí)間為1毫秒,最長(zhǎng)為4毫秒。這與平均停頓時(shí)間超過(guò)200毫秒的G1和Parallel形成鮮明的對(duì)比。
\\未來(lái)的可能性
\\彩色指針和加載屏障為我們帶來(lái)了一些有趣的未來(lái)可能性。
\\多層堆和壓縮
\\隨著閃存和非易失性內(nèi)存變得越來(lái)越普及,JVM的多層堆將成為可能,在多層堆中,很少被訪問(wèn)的存活對(duì)象將被保存在較慢的內(nèi)存層中。
\\我們可以對(duì)指針元數(shù)據(jù)進(jìn)行擴(kuò)展,加入一些計(jì)數(shù)器位,并使用這些位信息來(lái)決定是否需要移動(dòng)對(duì)象。在需要使用對(duì)象的時(shí)候,可以通過(guò)加載屏障從相應(yīng)的內(nèi)存層獲取對(duì)象。
\\或者也可以不將對(duì)象重定位到較慢的內(nèi)存層,而是將對(duì)象保存在主內(nèi)存中,不過(guò)需要對(duì)其進(jìn)行壓縮。在請(qǐng)求對(duì)象時(shí),通過(guò)加載屏障解對(duì)其進(jìn)行解壓并分配到堆中。
\\ZGC的狀態(tài)
\\在撰寫(xiě)本文時(shí),ZGC還處在實(shí)驗(yàn)階段。讀者可以通過(guò)Java 11 Early Access版本(http://jdk.java.net/11/)來(lái)體驗(yàn)ZGC,但需要指出的是,要解決一個(gè)新垃圾回收器存在的所有問(wèn)題可能需要很長(zhǎng)的一段時(shí)間。G1從發(fā)布到脫離實(shí)驗(yàn)階段花了至少三年時(shí)間。
\\總結(jié)
\\服務(wù)器擁有數(shù)百GB甚至是數(shù)TB的內(nèi)存變得越來(lái)越普及,Java有效使用內(nèi)存堆的能力變得越來(lái)越重要。ZGC是一個(gè)令人興奮的新型垃圾回收器,致力于大幅降低大堆垃圾回收的停頓時(shí)間。它通過(guò)使用彩色指針和加載屏障來(lái)實(shí)現(xiàn)這一點(diǎn),它們都是Hotspot新引入的GC技術(shù),并帶來(lái)了一些有趣的未來(lái)可能性。ZGC將作為Java 11的實(shí)驗(yàn)性垃圾回收器,讀者現(xiàn)在可以通過(guò)Java 11 Early Access體驗(yàn)ZGC。
\\英文原文:https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/
總結(jié)
以上是生活随笔為你收集整理的Oracle即将发布的全新Java垃圾收集器 ZGC的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 搭建WeApacheb网站服务器
- 下一篇: Java之品优购课程讲义_day20(5