GC術(shù)語
為了理解ZGC如何匹配現(xiàn)有收集器乳蓄,以及如何實(shí)現(xiàn)新GC蚊逢,我們需要先了解一些術(shù)語缩功。最基本的垃圾收集涉及識(shí)別不再使用的內(nèi)存并使其可重用∏缂埃現(xiàn)代收集器在幾個(gè)階段進(jìn)行這一過程,對(duì)于這些階段我們往往有如下描述:
并行- 在JVM運(yùn)行時(shí)嫡锌,同時(shí)存在應(yīng)用程序線程和垃圾收集器線程虑稼。 并行階段是由多個(gè)gc線程執(zhí)行琳钉,即gc工作在它們之間分配。 不涉及GC線程是否需要暫停應(yīng)用程序線程蛛倦。
串行- 串行階段僅在單個(gè)gc線程上執(zhí)行歌懒。與之前一樣,它也沒有說明GC線程是否需要暫停應(yīng)用程序線程溯壶。
STW - STW階段及皂,應(yīng)用程序線程被暫停,以便gc執(zhí)行其工作且改。 當(dāng)應(yīng)用程序因?yàn)镚C暫停時(shí)验烧,這通常是由于Stop The World(世界靜止?又跛?有丶中二)階段碍拆。
并發(fā) -如果一個(gè)階段是并發(fā)的,那么GC線程可以和應(yīng)用程序線程同時(shí)進(jìn)行慨蓝。 并發(fā)階段很復(fù)雜感混,因?yàn)樗鼈冃枰陔A段完成之前處理從而可能使工作無效(因?yàn)槭遣l(fā)進(jìn)行的,GC線程在完成一階段的同時(shí)礼烈,應(yīng)用線程也在工作產(chǎn)生操作內(nèi)存浩习,所以需要額外處理)的應(yīng)用程序線程。
增量 -如果一個(gè)階段是增量的济丘,那么它可以運(yùn)行一段時(shí)間之后由于某些條件提前終止,例如需要執(zhí)行更高優(yōu)先級(jí)的gc階段洽蛀,同時(shí)仍然完成生產(chǎn)性工作摹迷。 增量階段與需要完全完成的階段形成鮮明對(duì)比。
權(quán)衡取舍
這些屬性都需要權(quán)衡利弊郊供。 例如峡碉,并行階段將利用多個(gè)gc線程來執(zhí)行工作,但這樣做會(huì)導(dǎo)致線程協(xié)調(diào)的開銷驮审。 同樣鲫寄,并發(fā)階段不會(huì)暫停應(yīng)用程序線程,但可能涉及更多的開銷和復(fù)雜性疯淫,才能同時(shí)處理使其工作無效的應(yīng)用程序線程地来。
ZGC
現(xiàn)在我們了解不同gc階段的屬性,讓我們繼續(xù)探討ZGC的工作原理熙掺。 為了實(shí)現(xiàn)其目標(biāo)未斑,ZGC給Hotspot Garbage Collectors增加了兩種新技術(shù):著色指針和讀屏障。
著色指針
著色指針是一種將信息存儲(chǔ)在指針(或Java術(shù)語引用)中的技術(shù)币绩。因?yàn)樵?4位平臺(tái)上(ZGC僅支持linux64)蜡秽,指針可以處理更多的內(nèi)存府阀,因此可以使用一些位來存儲(chǔ)狀態(tài)。 ZGC將限制最大支持4Tb堆(42-bits)芽突,那么會(huì)剩下22位可用试浙,它目前使用了4位: finalizable, remap寞蚌, mark0和mark1田巴。 我們稍后解釋它們的用途。
著色指針的一個(gè)問題是睬澡,當(dāng)您需要取消著色時(shí)固额,它需要額外的工作(因?yàn)樾枰帘涡畔⑽唬?像SPARC這樣的平臺(tái)有內(nèi)置硬件支持指針屏蔽所以不是問題,而對(duì)于x86平臺(tái)來說煞聪,ZGC團(tuán)隊(duì)使用了簡(jiǎn)潔的多重映射技巧斗躏。
多重映射
要了解多重映射的工作原理,我們需要簡(jiǎn)要解釋虛擬內(nèi)存和物理內(nèi)存之間的區(qū)別昔脯。 物理內(nèi)存是系統(tǒng)可用的實(shí)際內(nèi)存啄糙,通常是安裝的DRAM芯片的容量。 虛擬內(nèi)存是抽象的云稚,這意味著應(yīng)用程序?qū)ΓㄍǔJ歉綦x的)物理內(nèi)存有自己的視圖隧饼。 操作系統(tǒng)負(fù)責(zé)維護(hù)虛擬內(nèi)存和物理內(nèi)存范圍之間的映射,它通過使用頁表和處理器的內(nèi)存管理單元(MMU)和轉(zhuǎn)換查找緩沖器(TLB)來實(shí)現(xiàn)這一點(diǎn)静陈,后者轉(zhuǎn)換應(yīng)用程序請(qǐng)求的地址燕雁。
多重映射涉及將不同范圍的虛擬內(nèi)存映射到同一物理內(nèi)存。 由于設(shè)計(jì)中只有一個(gè)remap鲸拥,mark0和mark1在任何時(shí)間點(diǎn)都可以為1拐格,因此可以使用三個(gè)映射來完成此操作。 ZGC源代碼中有一個(gè)很好的圖表可以說明這一點(diǎn)刑赶。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í)運(yùn)行的代碼片段(即訪問對(duì)象上的非原始字段non-primitive field):
void printName(Person person){
String name = person.name; //會(huì)觸發(fā)讀屏障? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //因?yàn)槲覀円呀?jīng)加載了一個(gè)引用? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //來自堆的
System.out.println(name); //沒有直接讀屏障
}
在上面的代碼中捏浊,String name = person.name 訪問了堆上的person引用,然后將引用加載到本地的name變量撞叨。此時(shí)觸發(fā)讀屏障金踪。 Systemt.out那行不會(huì)直接觸發(fā)讀屏障,因?yàn)闆]有來自堆的引用加載(name是局部變量牵敷,因此沒有從堆加載引用)胡岔。 但是System,out枷餐,println內(nèi)部可能會(huì)觸發(fā)其他讀屏障姐军。
這與其他GC使用的寫屏障形成對(duì)比,例如G1。讀屏障的工作是檢查引用的狀態(tài)奕锌,并在將引用(或者甚至是不同的引用)返回給應(yīng)用程序之前執(zhí)行一些工作著觉。 在ZGC中,它通過測(cè)試加載的引用來執(zhí)行此任務(wù)惊暴,以查看是否設(shè)置了某些位饼丘。 如果通過了測(cè)試,則不執(zhí)行任何其他工作辽话,如果沒通過肄鸽,則在將引用返回給應(yīng)用程序之前執(zhí)行某些特定于階段的任務(wù)。
標(biāo)記
現(xiàn)在我們了解了這兩種新技術(shù)是什么油啤,讓我們來看看ZG的GC循環(huán)典徘。
GC循環(huán)的第一部分是標(biāo)記。標(biāo)記包括查找和標(biāo)記運(yùn)行中的應(yīng)用程序可以訪問的所有堆對(duì)象益咬,換句話說逮诲,查找不是垃圾的對(duì)象。
ZGC的標(biāo)記分為三個(gè)階段幽告。 第一階段是STW梅鹦,其中GC roots被標(biāo)記為活對(duì)象。 GC roots類似于局部變量冗锁,通過它可以訪問堆上其他對(duì)象齐唆。 如果一個(gè)對(duì)象不能通過遍歷從roots開始的對(duì)象圖來訪問,那么應(yīng)用程序也就無法訪問它冻河,則該對(duì)象被認(rèn)為是垃圾箍邮。從roots訪問的對(duì)象集合稱為L(zhǎng)ive集。GC roots標(biāo)記步驟非常短叨叙,因?yàn)閞oots的總數(shù)通常比較小锭弊。
該階段完成后,應(yīng)用程序恢復(fù)執(zhí)行摔敛,ZGC開始下一階段,該階段同時(shí)遍歷對(duì)象圖并標(biāo)記所有可訪問的對(duì)象全封。 在此階段期間马昙,讀屏障針使用掩碼測(cè)試所有已加載的引用,該掩碼確定它們是否已標(biāo)記或尚未標(biāo)記刹悴,如果尚未標(biāo)記引用行楞,則將其添加到隊(duì)列以進(jìn)行標(biāo)記。
在遍歷完成之后土匀,有一個(gè)最終的子房,時(shí)間很短的的STW階段,這個(gè)階段處理一些邊緣情況(我們現(xiàn)在將它忽略),該階段完成之后標(biāo)記階段就完成了证杭。
重定位
GC循環(huán)的下一個(gè)主要部分是重定位田度。重定位涉及移動(dòng)活動(dòng)對(duì)象以釋放部分堆內(nèi)存。 為什么要移動(dòng)對(duì)象而不是填補(bǔ)空隙解愤? 有些GC實(shí)際是這樣做的镇饺,但是它導(dǎo)致了一個(gè)不幸的后果,即分配內(nèi)存變得更加昂貴送讲,因?yàn)楫?dāng)需要分配內(nèi)存時(shí)奸笤,內(nèi)存分配器需要找到可以放置對(duì)象的空閑空間。 相比之下哼鬓,如果可以釋放大塊內(nèi)存监右,那么分配內(nèi)存就很簡(jiǎn)單,只需要將指針遞增新對(duì)象所需的內(nèi)存大小即可异希。
ZGC將堆分成許多頁面健盒,在此階段開始時(shí),它同時(shí)選擇一組需要重定位活動(dòng)對(duì)象的頁面宠互。選擇重定位集后味榛,會(huì)出現(xiàn)一個(gè)Stop The World暫停,其中ZGC重定位該集合中root對(duì)象予跌,并將他們的引用映射到新位置搏色。與之前的Stop The World步驟一樣,此處涉及的暫停時(shí)間僅取決于root的數(shù)量以及重定位集的大小與對(duì)象的總活動(dòng)集的比率券册,這通常相當(dāng)小频轿。所以不像很多收集器那樣,暫停時(shí)間隨堆增加而增加烁焙。
移動(dòng)root后航邢,下一階段是并發(fā)重定位。 在此階段骄蝇,GC線程遍歷重定位集并重新定位其包含的頁中所有對(duì)象膳殷。 如果應(yīng)用程序線程試圖在GC重新定位對(duì)象之前加載它們,那么應(yīng)用程序線程也可以重定位該對(duì)象九火,這可以通過讀屏障(在從堆加載引用時(shí)觸發(fā))實(shí)現(xiàn)赚窃,如流程圖如下所示:
這可確保應(yīng)用程序看到的所有引用都已更新,并且應(yīng)用程序不可能同時(shí)對(duì)重定位的對(duì)象進(jìn)行操作岔激。
GC線程最終將對(duì)重定位集中的所有對(duì)象重定位勒极,然而可能仍有引用指向這些對(duì)象的舊位置。 GC可以遍歷對(duì)象圖并重新映射這些引用到新位置虑鼎,但是這一步代價(jià)很高昂辱匿。 因此這一步與下一個(gè)標(biāo)記階段合并在一起键痛。在下一個(gè)GC周期的標(biāo)記階段遍歷對(duì)象對(duì)象圖的時(shí)候,如果發(fā)現(xiàn)未重映射的引用匾七,則將其重新映射絮短,然后標(biāo)記為活動(dòng)狀態(tài)。
概括
試圖單獨(dú)理解復(fù)雜垃圾收集器(如ZGC)的性能特征是很困難的乐尊,但從前面的部分可以清楚地看出戚丸,我們所碰到的幾乎所有暫停都只依賴于GC roots集合大小,而不是實(shí)時(shí)堆大小扔嵌。標(biāo)記階段中處理標(biāo)記終止的最后一次暫停是唯一的例外限府,但是它是增量的,如果超過gc時(shí)間預(yù)算痢缎,那么GC將恢復(fù)到并發(fā)標(biāo)記胁勺,直到再次嘗試。
概括
試圖單獨(dú)理解復(fù)雜垃圾收集器(如ZGC)的性能特征是很困難的独旷,但從前面的部分可以清楚地看出署穗,我們所碰到的幾乎所有暫停都只依賴于GC roots集合大小,而不是實(shí)時(shí)堆大小嵌洼。標(biāo)記階段中處理標(biāo)記終止的最后一次暫停是唯一的例外案疲,但是它是增量的,如果超過gc時(shí)間預(yù)算麻养,那么GC將恢復(fù)到并發(fā)標(biāo)記褐啡,直到再次嘗試。
性能
那ZGC到底表現(xiàn)如何鳖昌?
Stefan Karlsson和Per Liden在今年早些時(shí)候的Jfokus演講中給出了一些數(shù)字备畦。 ZGC的SPECjbb 2015吞吐量與Parallel GC(優(yōu)化吞吐量)大致相當(dāng),但平均暫停時(shí)間為1ms许昨,最長(zhǎng)為4ms懂盐。 與之相比G1和Parallel有很多次超過200ms的GC停頓。
未來的可能性
著色指針和讀屏障提供了一些有趣的可能糕档。
多層堆和壓縮
隨著閃存和非易失性存儲(chǔ)器變得越來越普遍莉恼,一種可能是JVM中允許多層堆,可以讓很少使用的對(duì)象存儲(chǔ)在較慢的存儲(chǔ)層上速那。
該功能可以通過擴(kuò)展指針元數(shù)據(jù)來實(shí)現(xiàn)俐银,指針可以實(shí)現(xiàn)計(jì)數(shù)器位并使用該信息來決定是否需要移動(dòng)對(duì)象到較慢的存儲(chǔ)上。如果將來需要訪問琅坡,則讀屏障可以從存儲(chǔ)中檢索到對(duì)象悉患。
或者對(duì)象可以以壓縮形式保存在內(nèi)存中残家,而不是將對(duì)象重定位到較慢的存儲(chǔ)層榆俺。當(dāng)請(qǐng)求時(shí),可以通過讀屏障將其解壓并重新分配。
原文章:https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/?from=timeline
更多資料:https://www.zhihu.com/question/287945354/answer/458761494(R大)
ZGC使用
1.構(gòu)建時(shí)需要額外指定
--with-jvm-features=zgc
2.僅支持linux/64
-XX:+UnlockExperimentalVMOptions-XX:+UseZGC