Z Garbage Collector,即ZGC,是一個(gè)可伸縮的、低延遲的垃圾收集器,主要為了滿足如下目標(biāo)進(jìn)行設(shè)計(jì):
- 停頓時(shí)間不會(huì)超過10ms
- 停頓時(shí)間不會(huì)隨著堆的增大而增大(不管多大的堆都能保持在10ms以下)
- 可支持幾百M(fèi),甚至幾T的堆大?。ㄗ畲笾С?T)
停頓時(shí)間在10ms以下,10ms其實(shí)是一個(gè)很保守的數(shù)據(jù),在SPECjbb 2015基準(zhǔn)測(cè)試,128G的大堆下最大停頓時(shí)間才1.68ms,遠(yuǎn)低于10ms,和G1算法相比,也感覺像是在虐菜。

G1算法通過只回收部分Region,避免了全堆掃描,改善了大堆下的停頓時(shí)間,但在普通大小的堆里卻表現(xiàn)平平,ZGC為什么可以這么優(yōu)秀,主要是因?yàn)橐韵聨讉€(gè)特性。
Concurrent
ZGC只有短暫的STW,大部分的過程都是和應(yīng)用線程并發(fā)執(zhí)行,比如最耗時(shí)的并發(fā)標(biāo)記和并發(fā)移動(dòng)過程。
Region-based
ZGC中沒有新生代和老年代的概念,只有一塊一塊的內(nèi)存區(qū)域page,以page單位進(jìn)行對(duì)象的分配和回收。
Compacting
每次進(jìn)行GC時(shí),都會(huì)對(duì)page進(jìn)行壓縮操作,所以完全避免了CMS算法中的碎片化問題。
NUMA-aware
現(xiàn)在多CPU插槽的服務(wù)器都是Numa架構(gòu),比如兩顆CPU插槽(24核),64G內(nèi)存的服務(wù)器,那其中一顆CPU上的12個(gè)核,訪問從屬于它的32G本地內(nèi)存,要比訪問另外32G遠(yuǎn)端內(nèi)存要快得多。
ZGC默認(rèn)支持NUMA架構(gòu),在創(chuàng)建對(duì)象時(shí),根據(jù)當(dāng)前線程在哪個(gè)CPU執(zhí)行,優(yōu)先在靠近這個(gè)CPU的內(nèi)存進(jìn)行分配,這樣可以顯著的提高性能,在SPEC JBB 2005 基準(zhǔn)測(cè)試?yán)铽@得40%的提升。
Using colored pointers
和以往的標(biāo)記算法比較不同,CMS和G1會(huì)在對(duì)象的對(duì)象頭進(jìn)行標(biāo)記,而ZGC是標(biāo)記對(duì)象的指針。

其中低42位對(duì)象的地址,42-45位用來做指標(biāo)標(biāo)記。
Using load barriers
因?yàn)樵跇?biāo)記和移動(dòng)過程中,GC線程和應(yīng)用線程是并發(fā)執(zhí)行的,所以存在這種情況:對(duì)象A內(nèi)部的引用所指的對(duì)象B在標(biāo)記或者移動(dòng)狀態(tài),為了保證應(yīng)用線程拿到的B對(duì)象是對(duì)的,那么在讀取B的指針時(shí)會(huì)經(jīng)過一個(gè) “l(fā)oad barriers” 讀屏障,這個(gè)屏障可以保證在執(zhí)行GC時(shí),數(shù)據(jù)讀取的正確性。
一些變化
JDK11
- ZGC的最初版本
- 不支持類卸載class unloading (using -XX:+ClassUnloading has no effect)
JDK12 - 進(jìn)一步減少停頓時(shí)間
- 支持類卸載功能
平臺(tái)支持
ZGC目前只在Linux/x64上可用,如果有足夠的需求,將來可能會(huì)增加對(duì)其他平臺(tái)的支持。
目前只支持64位的linux系統(tǒng),狼哥在mac跑了半天都是下面的錯(cuò)!

如何編譯
$ hg clone https://wiki.openjdk.java.net/display/hg.openjdk.java.net/jdk/jdk
$ cd jdk
$ sh configure
$ make images
如果正在編譯的版本是 11.0.0, 11.0.1 or 11.0.2,必須加上配置參數(shù)--with-jvm-features=zgc開啟ZGC的編譯,在11.0.3或者12之后,可以忽略這個(gè)參數(shù),已經(jīng)默認(rèn)支持。
編譯結(jié)束之后,你會(huì)得到一個(gè)完整的JDK,在Linux中,可以在下面目錄中找到這個(gè)新的JDK
./build/linux-x86_64-normal-server-release/images/jdk
可以進(jìn)入bin文件夾,執(zhí)行 ./java -version 驗(yàn)證一下。
如何使用
編譯完成之后,已經(jīng)迫不及待的想試試ZGC,需要配置以下JVM參數(shù),才能使用ZGC.
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx10g -Xlog:gc
參數(shù)說明:
Heap Size
通過-Xmx10g進(jìn)行設(shè)置。
-Xmx是ZGC收集器中最重要的調(diào)優(yōu)選項(xiàng),大大解決了程序員在JVM參數(shù)調(diào)優(yōu)上的困擾。ZGC是一個(gè)并發(fā)收集器,必須要設(shè)置一個(gè)最大堆的大小,應(yīng)用需要多大的堆,主要有下面幾個(gè)考量:
- 對(duì)象的分配速率,要保證在GC的時(shí)候,堆中有足夠的內(nèi)存分配新對(duì)象
- 一般來說,給ZGC的內(nèi)存越多越好,但是也不能浪費(fèi)內(nèi)存,所以要找到一個(gè)平衡。
Concurrent GC Threads
通過-XX:ConcGCThread = 4進(jìn)行設(shè)置。
并發(fā)執(zhí)行的GC線程數(shù),如果沒有設(shè)置,在JVM啟動(dòng)的時(shí)候會(huì)根據(jù)CPU的核數(shù)計(jì)算出一個(gè)合理的數(shù)量,默認(rèn)是核數(shù)的12.5%,但是根據(jù)應(yīng)用的特性,可以通過手動(dòng)設(shè)置調(diào)整。
因?yàn)樵诓l(fā)標(biāo)記和并發(fā)移動(dòng)時(shí),GC線程和應(yīng)用線程是并發(fā)執(zhí)行的,所以存在搶占CPU的情況,對(duì)于一些對(duì)延遲比較敏感的應(yīng)用,這個(gè)并發(fā)線程數(shù)就不能設(shè)置的過大,不然會(huì)降低應(yīng)用的吞吐量,并有可能增加應(yīng)用的延遲,因?yàn)镚C線程占用了太多的CPU,但是如果設(shè)置的太小,就有可能對(duì)象的分配速率比垃圾收集的速率來的大,最終導(dǎo)致應(yīng)用線程停下來等GC線程完成垃圾收集,并釋放內(nèi)存。
一般來說,如果低延遲對(duì)應(yīng)用程序很重要,那么不要這個(gè)值不要設(shè)置的過于大,理想情況下,系統(tǒng)的CPU利用率不應(yīng)該超過70%。
Parallel GC Threads
通過-XX:ParallelGCThreads = 20
當(dāng)對(duì)GC Roots進(jìn)行標(biāo)記和移動(dòng)時(shí),需要進(jìn)行STW,這個(gè)過程會(huì)使用ParallelGCThreads個(gè)GC線程進(jìn)行并行執(zhí)行。
ParallelGCThreads默認(rèn)為CPU核數(shù)的60%,為什么可以這么大?
因?yàn)檫@個(gè)時(shí)候,應(yīng)用線程已經(jīng)完全停下來了,所以要用盡可能多的線程完成這部分任務(wù),這樣才能讓STW盡可能的短暫。