1. ZGC介紹
ZGC(The Z Garbage Collector)是 JDK 11 中推出的一款低延遲垃圾回收器,為實(shí)現(xiàn)以下幾個(gè)目標(biāo)而誕生的垃圾回收器,停頓時(shí)間不超過 10ms,停頓時(shí)間不會(huì)因堆變大而變長,支持 8MB~4TB 級(jí)別的堆(未來支持 16TB)
2. ZGC內(nèi)存和原理
2.1 ZGC內(nèi)存分布
ZGC 與傳統(tǒng)的 CMS、G1 不同、它沒有分代的概念,只有類似 G1 的 Region 概率;在劃分內(nèi)存的方式上,ZGC 與 G1 有著相似的地方,都舍棄了年輕代、老年代的劃分方式,但是又與 G1 的形式不太一樣,ZGC 是頁為單位進(jìn)行劃分,一般分為三種頁面:
- 小型 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ì)象,這也預(yù)示著雖然名字叫 “大型 Region”,但它的實(shí)際容量完全有可能小于中型 Region,最小容量可低至 4MB。大型 Region 在 ZGC 的實(shí)現(xiàn)中是不會(huì)被重分配的(重分配是 ZGC 的一種處理動(dòng)作,用于復(fù)制對(duì)象的收集器階段)因?yàn)閺?fù)制大對(duì)象的代價(jià)非常高。
2.2 ZGC 原理
全并發(fā)的 ZGC垃圾回收流程: 與 CMS 中的 ParNew 和 G1 類似,ZGC 也采用標(biāo)記 - 復(fù)制算法,不過 ZGC 對(duì)該算法做了重大改進(jìn):ZGC 在標(biāo)記、轉(zhuǎn)移和重定位階段幾乎都是并發(fā)的,這是 ZGC 實(shí)現(xiàn)停頓時(shí)間小于 10ms 目標(biāo)的最關(guān)鍵原因。
ZGC 垃圾回收周期如下圖所示:
ZGC 只有三個(gè) STW 階段:初始標(biāo)記,再標(biāo)記,初始轉(zhuǎn)移
初始標(biāo)記和初始轉(zhuǎn)移分別都只需要掃描所有 GC Roots,其處理時(shí)間和 GC Roots 的數(shù)量成正比,一般情況耗時(shí)非常短;再標(biāo)記階段 STW 時(shí)間很短,最多 1ms,超過 1ms 則再次進(jìn)入并發(fā)標(biāo)記階段。即,ZGC 幾乎所有暫停都只依賴于 GC Roots 集合大小,停頓時(shí)間不會(huì)隨著堆的大小或者活躍對(duì)象的大小而增加。與 ZGC 對(duì)比,G1 的轉(zhuǎn)移階段完全 STW 的,且停頓時(shí)間隨存活對(duì)象的大小增加而增加。
3. ZGC技術(shù)特性
3.1 著色指針
著色指針(Colored Pointers)是 ZGC 的關(guān)鍵技術(shù)。ZGC 之前的垃圾回收器 JVM 將對(duì)象的 GC 信息記錄在對(duì)象頭 Mark Word 中,GC 進(jìn)行標(biāo)記的時(shí)候遍歷 GC Roots 的對(duì)象然后對(duì) Mark Word 的信息進(jìn)行修改。而 ZGC 將 GC 信息記錄在指針中,標(biāo)記算法不再尋找 Mark Word 中的信息,只需要找到相應(yīng)的指針信息即可。
在 64 位架構(gòu)的計(jì)算機(jī)中(ZGC 只支持 64 位), 一個(gè) Java 對(duì)象 64 位,其中低位的 42 位是對(duì)象地址,42-45 位用來做標(biāo)記信息,四個(gè)狀態(tài)分別是 Marked0、Marked1、Remapped、Finalizable,剩下的 18 位預(yù)留以后使用。
創(chuàng)建對(duì)象時(shí),JVM 先在堆空間申請(qǐng)一個(gè)內(nèi)存地址,同時(shí)利用 MMAP 函數(shù)將該內(nèi)存地址分別映射到 Marked0、Marked1,Remapped 完成多視圖映射。在同一時(shí)間這三個(gè)視圖有且僅有一個(gè)生效,這是一種 “空間換時(shí)間” 的思想。
3.2 讀屏障
讀屏障主要是用來解決指針在并行轉(zhuǎn)移的過程中出現(xiàn)的問題。ZGC 進(jìn)行并行轉(zhuǎn)移時(shí),GC 線程與 Java 應(yīng)用線程同時(shí)工作,當(dāng) Java 應(yīng)用線程讀取一個(gè)未完成轉(zhuǎn)移的對(duì)象的時(shí)候就會(huì)出現(xiàn)指針無效的問題。為了解決這個(gè)問題 ZGC 使用了讀屏障的技術(shù),當(dāng)出現(xiàn)上述情況的時(shí)候,Java 應(yīng)用線程必須在讀取對(duì)象之前先把對(duì)象轉(zhuǎn)移。同時(shí) ZGC 設(shè)置了觸發(fā)條件,只有在應(yīng)用線程從內(nèi)存堆中加載對(duì)象引用的情況下才會(huì)觸發(fā)讀屏障。讀屏障根據(jù)著色指針記錄的 GC 信息判斷對(duì)象是否被移動(dòng)過,如果對(duì)象發(fā)生過移動(dòng)就需要對(duì)指針的內(nèi)存地址進(jìn)行修復(fù)。讀屏障的觸發(fā)條件可參考以下代碼:
Object object = obj.FieldA ; // 從堆中讀取對(duì)象引用,需要加入讀屏障
Object o = object ; // 不需要加入讀屏障,因?yàn)椴皇菑亩阎凶x取引用
object.doSomething (); // 不需要加入讀屏障,因?yàn)椴皇菑亩阎凶x取引用
int i = obj.FieldB; // 不需要加入讀屏障,因?yàn)椴皇菍?duì)象引用
ZGC 中讀屏障的代碼作用:
GC 線程和應(yīng)用線程是并發(fā)執(zhí)行的,所以存在應(yīng)用線程去 A 對(duì)象內(nèi)部的引用所指向的對(duì)象 B 的時(shí)候,這個(gè)對(duì)象 B 正在被 GC 線程移動(dòng)或者其他操作,加上讀屏障之后,應(yīng)用線程會(huì)去探測對(duì)象 B 是否被 GC 線程操作,然后等待操作完成再讀取對(duì)象,確保數(shù)據(jù)的準(zhǔn)確性。具體的探測和操作步驟如下:
4 JVM參數(shù)解析
4.1 JVM參數(shù)使用
下面是一些通用 GC 參數(shù)和 ZGC 特有參數(shù)以及 ZGC 的一些診斷選型,來自官網(wǎng):
對(duì)比G1和ZGC JVM參數(shù):
- JKD8 G1 的啟動(dòng)參數(shù):
-server -Xms1024m -Xmx1024m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+PrintReferenceGC
-XX:+ParallelRefProcEnabled
-XX:G1HeapRegionSize=16m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/apps/errorDump.hprof
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-XX:+PrintGCApplicationConcurrentTime
-verbose:gc
-Xloggc:/opt/apps/logs/${app_name}-gc.log
- JDK 9開始部分JVM參數(shù)已移除:
CMSDumpAtPromotionFailure
CMSPrintEdenSurvivorChunks
G1LogLevel
G1PrintHeapRegions
G1PrintRegionLivenessInfo
G1SummarizeConcMark
G1SummarizeRSetStats
G1TraceConcRefinement
G1TraceEagerReclaimHumongousObjects
G1TraceStringSymbolTableScrubbing
GCLogFileSize
NumberOfGCLogFiles
PrintAdaptiveSizePolicy
PrintClassHistogramAfterFullGC
PrintClassHistogramBeforeFullGC
PrintCMSInitiationStatistics
PrintCMSStatistics
PrintFLSCensus
PrintFLSStatistics
PrintGC
PrintGCApplicationConcurrentTime
PrintGCApplicationStoppedTime
PrintGCCause
PrintGCDateStamps
PrintGCDetails
PrintGCID
PrintGCTaskTimeStamps
PrintGCTimeStamps
PrintHeapAtGC
PrintHeapAtGCExtended
PrintJNIGCStalls
PrintOldPLAB
PrintParallelOldGCPhaseTimes
PrintPLAB
PrintPromotionFailure
PrintReferenceGC
PrintStringDeduplicationStatistics
PrintTaskqueue
PrintTenuringDistribution
PrintTerminationStats
PrintTLAB
TraceDynamicGCThreads
TraceMetadataHumongousAllocation
UseGCLogFileRotation
VerifySilently
-Xloggc
- JAVA11 G1 啟動(dòng)參數(shù)如下:
-server -Xms1024m -Xmx1024m -Xss256k
-XX:+UseG1GC
-XX:MaxDirectMemorySize=256m
-XX:+UseCompressedOops
-XX:+UseCompressedClassPointers
-XX:+SegmentedCodeCache
-verbose:gc
-XX:+PrintCommandLineFlags
-XX:+ExplicitGCInvokesConcurrent
-Djava.security.egd=file:/dev/./urandom
-Xlog:gc*,safepoint:/data/log/${app_name}-gc.log:time,uptime:filecount=100,filesize=50M
- JDK17 ZGC 的啟動(dòng)參數(shù)如下:
-server -Xms1024m -Xmx1024m
#開啟ZGC
-XX:+UseZGC
#GC周期之間的最大間隔(單位秒)
-XX:ZCollectionInterval=120
#官方的解釋是 ZGC 的分配尖峰容忍度,數(shù)值越大越早觸發(fā)GC
-XX:ZAllocationSpikeTolerance=4
#關(guān)閉主動(dòng)GC周期,在主動(dòng)回收模式下,ZGC 會(huì)在系統(tǒng)空閑時(shí)自動(dòng)執(zhí)行垃圾回收,以減少垃圾回收在應(yīng)用程序忙碌時(shí)所造成的影響。如果未指定此參數(shù)(默認(rèn)情況),ZGC 會(huì)在需要時(shí)(即堆內(nèi)存不足以滿足分配請(qǐng)求時(shí))執(zhí)行垃圾回收。
-XX:-ZProactive
#GC日志
-Xlog:safepoint=trace,classhisto*=trace,age*=info,gc*=info:file=/opt/logs/gc-%t.log:time,level,tid,tags:filesize=50M
#發(fā)生OOM時(shí)dump內(nèi)存日志
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/apps/errorDump.hprof
GC 日志中包含有關(guān) GC 操作的詳細(xì)信息,可以幫我們分析當(dāng)前 GC 存在的問題。先來看一下上面 JVM 參數(shù)中關(guān)于 GC 日志的參數(shù):
-
safepoint=trace:記錄關(guān)于 safepoint 的 trace 級(jí)別日志。 Safepoint 是 JVM 中一個(gè)特殊的狀態(tài),它用于確保所有線程在特定操作(如垃圾回收、代碼優(yōu)化等)之前進(jìn)入安全狀態(tài)。
-
classhisto*=trace:記錄與類的歷史相關(guān)的 trace 級(jí)別日志。 age*=info:記錄與對(duì)象年齡(在新生代中存在的時(shí)間)相關(guān)的 info 級(jí)別日志。
-
gc*=info:記錄與垃圾回收相關(guān)的 info 級(jí)別日志。
-
file=/opt/logs/gc-% t.log:將日志寫入到 /opt/logs/ 目錄下的文件中,文件名為 gc-% t.log,其中 % t 是一個(gè)占位符,表示當(dāng)前時(shí)間戳。
-
time,level,tid,tags:在每個(gè)日志記錄中包含時(shí)間戳、日志級(jí)別、線程 ID 和標(biāo)簽。
-
filesize=50M:設(shè)置日志文件的大小限制為 50MB。當(dāng)日志文件大小達(dá)到此限制時(shí),JVM 將創(chuàng)建一個(gè)新的日志文件并繼續(xù)記錄。
更詳細(xì)的 gc 日志配置可以參考:https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#enable-logging-with-the-jvm-unified-logging-framework
4.2 STW 日志解析
其中我們重點(diǎn)關(guān)注的就是 GC 的 STW 情況,以下是一些關(guān)鍵字代表 GC STW 階段
-
最基本的 STW 三階段,初始標(biāo)記:日志中 Pause Mark Start,再標(biāo)記:日志中 Pause Mark End,初始轉(zhuǎn)移:日志中 Pause Relocate Start。
-
內(nèi)存分配阻塞:這一般是因?yàn)槔a(chǎn)速度大于回收速度,垃圾來不及回收,垃圾將堆占滿時(shí),線程會(huì)阻塞等待 GC 完成,關(guān)鍵字是 Allocation Stall(被阻塞的線程名稱)
如果出現(xiàn)此類日志,可以嘗試如下方法解決: -
-XX:ZCollectionInterval 該配置含義:兩個(gè) GC 周期之間的最大間隔(單位秒)。默認(rèn)情況下,此選項(xiàng)設(shè)置為 0(禁用),可以適當(dāng)調(diào)小該配置,讓 GC 周期縮短、提升垃圾回收速度,但這會(huì)提升應(yīng)用 CPU 占用。
-
-XX:ZAllocationSpikeTolerance 官方的解釋是 ZGC 的分配尖峰容忍度。其實(shí)就是數(shù)值越大,越早觸發(fā)回收??梢赃m當(dāng)調(diào)大該配置,更早觸發(fā)回收,提升垃圾回收速度,但這會(huì)提升應(yīng)用 CPU 占用。
-
安全點(diǎn):所有線程進(jìn)入到安全點(diǎn)后才能進(jìn)行 GC,ZGC 定期進(jìn)入安全點(diǎn)判斷是否需要 GC。先進(jìn)入安全點(diǎn)的線程需要等待后進(jìn)入安全點(diǎn)的線程直到所有線程掛起。日志關(guān)鍵字 safepoint … stopped
-
dump 線程、內(nèi)存:比如 jstack、jmap 命令,一般是手動(dòng) dump 導(dǎo)致,日志關(guān)鍵字 HeapDumper文章來源:http://www.zghlxwxcb.cn/news/detail-541231.html
5 總結(jié)
ZGC 作為下一代垃圾回收器,性能非常優(yōu)秀。ZGC 垃圾回收過程幾乎全部是并發(fā),實(shí)際 STW 停頓時(shí)間極短,不到 10ms。這得益于其采用的著色指針和讀屏障技術(shù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-541231.html
到了這里,關(guān)于JVM — JDK11垃圾回收器 ZGC的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!