jdk11-zgc-gc時(shí)間不斷增長(zhǎng)

1.現(xiàn)象

為了降低gc時(shí)間,我們打算對(duì)一批服務(wù)安裝jdk11,使用zgc。在對(duì)zgc進(jìn)行測(cè)試期間,發(fā)現(xiàn)隨著程序的運(yùn)行,gc時(shí)間越來越長(zhǎng)。如下圖所示:

每分鐘gc總耗時(shí)

同時(shí)進(jìn)程的gc次數(shù)并沒有發(fā)生太大變化,如下圖所示:

每分鐘gc總次數(shù)

zgc的 gc.time 只和 GC Roots 相關(guān),關(guān)于zgc可以參考:https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html

既然gc次數(shù)沒有變多,所以應(yīng)該是GC Roots 數(shù)量增長(zhǎng)

2.原因分析

我們?nèi)ゲ榭唇鼛滋斓膅c日志,會(huì)發(fā)現(xiàn) Subphase: Pause Roots ClassLoaderDataGraph? 這個(gè)階段的耗時(shí)會(huì)不斷增加

gc日志

這時(shí)候開始懷疑GC Roots有問題

此時(shí)查看metaspace元空間監(jiān)控,發(fā)現(xiàn)metaspace不斷增長(zhǎng),metaspace里的對(duì)象是被認(rèn)為GC Roots。所以因?yàn)閙etaspace不斷增長(zhǎng),導(dǎo)致GC Roots越來越多,最終導(dǎo)致gc時(shí)間越來越長(zhǎng)

元空間內(nèi)存占用情況

為什么metasapce空間不斷增長(zhǎng)呢?

metaspace空間主要保存對(duì)象類信息,我們查看關(guān)于class相關(guān)監(jiān)控信息

加載class信息監(jiān)控

可以看到我們程序不斷l(xiāng)oad class,但是沒有unload class,所以導(dǎo)致meta內(nèi)存一直增長(zhǎng)

原因大概也就清楚了,但是此時(shí)產(chǎn)生疑問,為什么程序原先使用jdk8 cms gc算法,沒有這個(gè)問題呢。

我們查看一下之前使用cms算法相關(guān)監(jiān)控


cms機(jī)器監(jiān)控信息

可以看到這臺(tái) cms 機(jī)器,也不斷l(xiāng)oad class,但是他也會(huì) unload class,所以metaspace一直維持平穩(wěn)

因?yàn)槲覀冊(cè)谑褂胏ms時(shí),設(shè)置了一個(gè)jvm參數(shù):CMSClassUnloadingEnabled 。該參數(shù)表示在進(jìn)行cms,進(jìn)行class卸載

3. zgc

為什么zgc不會(huì)收 meta:使用的jdk11不支持回收,jdk12才支持

在JDK11,zgc垃圾回收器目前還只是實(shí)驗(yàn)性的功能,只支持Linux/x64平臺(tái)。后續(xù)優(yōu)化接改進(jìn),短時(shí)間內(nèi)無法更新到JDK11中,所以可能會(huì)遇到一些不穩(wěn)定因素。例如: 1. JDK12支持并發(fā)類卸載功能。2. JDK13將可回收內(nèi)存從4TB支持到16TB。3. JDK14提升穩(wěn)定性的同時(shí),提高性能。4. JDK15從實(shí)驗(yàn)特性轉(zhuǎn)變?yōu)榭缮a(chǎn)特性 。所以對(duì)于一些大量使用反射、動(dòng)態(tài)代理、CGLIB和Javasist等頻繁自定義類加載器的場(chǎng)景中,ZGC難以處理Metaspace的巨大內(nèi)存壓力。

4.為什么metaspace一直升高

我們對(duì)運(yùn)行的進(jìn)程進(jìn)行class統(tǒng)計(jì)

jcmd {pid} GC.class_stats

加載的class類信息

發(fā)現(xiàn)存在大量的jdk.internal.reflect.GeneratedConstructorAccessor class類

我們對(duì)上述的內(nèi)容進(jìn)行前綴統(tǒng)計(jì),命令如下:

?jcmd {pid} GC.class_stats |awk '{print$13}'|sed? 's/\(.*\)\.\(.*\)/\1/g'|sort |uniq -c|sort -nrk1

class前綴統(tǒng)計(jì)

發(fā)現(xiàn) jdk.internal.reflect 包名下的類文件,占用了很大一部分,并且隨著程序運(yùn)行,jdk.internal.reflect這個(gè)類文件越來越多。

這個(gè)就是我們?cè)谑褂胘dk反射時(shí),會(huì)自動(dòng)生成的類字節(jié)碼,被jvm加載進(jìn)metaspace。隨著程序運(yùn)行,加載類字節(jié)碼越來越多,但是沒有釋放,導(dǎo)致meta越來越大。

5.解決辦法

-Dsun.reflect.inflationThreshold 可以控制通過反射生成字節(jié)碼。(該值表示 反射調(diào)用多少次 才開始生成字節(jié)碼)

當(dāng)把該參數(shù)這是int 最大值時(shí),說明永不生成字節(jié)碼。

從stackoverflows摘抄一段話

When using Java reflection, the JVM has two methods of accessing the information on the class being reflected. It can use a JNI accessor, or a Java bytecode accessor. If it uses a Java bytecode accessor, then it needs to have its own Java class and classloader (sun/reflect/GeneratedMethodAccessor class and sun/reflect/DelegatingClassLoader). Theses classes and classloaders use native memory. The accessor bytecode can also get JIT compiled, which will increase the native memory use even more. If Java reflection is used frequently, this can add up to a significant amount of native memory use. The JVM will use the JNI accessor first, then after some number of accesses on the same class, will change to use the Java bytecode accessor. This is called inflation, when the JVM changes from the JNI accessor to the bytecode accessor. Fortunately, we can control this with a Java property. The sun.reflect.inflationThreshold property tells the JVM what number of times to use the JNI accessor. If it is set to 0, then the JNI accessors are always used. Since the bytecode accessors use more native memory than the JNI ones, if we are seeing a lot of Java reflection, we will want to use the JNI accessors. To do this, we just need to set the inflationThreshold property to zero.

If you are on a Oracle JVM then you would only need to set:? -Dsun.reflect.inflationThreshold=2147483647

If you are on IBM JVM, then you would need to set:? ?-Dsun.reflect.inflationThreshold=0

大概意思說:使用java反射,內(nèi)部實(shí)現(xiàn)有2種方式,一種是jni,另一種是生成字節(jié)碼方式。生成字節(jié)碼方式會(huì)占用更多內(nèi)存,但是性能會(huì)好一點(diǎn)。sun.reflect.inflationThreshold 代表,反射執(zhí)行多少次后,開始使用字節(jié)碼方式,當(dāng)我們把這個(gè)參數(shù)設(shè)置為int最大值,代表永不使用字節(jié)碼方式,也就沒有內(nèi)存問題了。

具體原理參考:

https://www.moregeek.xyz/i/880000804235

https://stackoverflow.com/questions/16130292/java-lang-outofmemoryerror-permgen-space-java-reflection

重新加上?-Dsun.reflect.inflationThreshold=2147483647 這個(gè)參數(shù),后來gc.time 次數(shù)就一直穩(wěn)定下來了,并且metaspace空間也不增長(zhǎng)了

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

  • 1、從編碼到執(zhí)行 解釋執(zhí)行和編譯執(zhí)行是可以混合的,執(zhí)行次數(shù)多的代碼,會(huì)進(jìn)行 JIT 的編譯,交由操作系統(tǒng)直接執(zhí)行。...
    ArthurHC閱讀 546評(píng)論 0 2
  • Catalog 1 怎么解決OOM?/ 怎么排查OOM?/ JVM調(diào)優(yōu)1.1 JDK自帶工具1.2 阿里開源JVM...
    allen鍋閱讀 548評(píng)論 0 1
  • 解決服務(wù)器進(jìn)程退出問題(metaspace溢出) 現(xiàn)象策劃反應(yīng)服務(wù)器進(jìn)不去,遠(yuǎn)程看了一下進(jìn)程消失了(crash)有...
    landon30閱讀 3,446評(píng)論 0 2
  • 運(yùn)行時(shí)數(shù)據(jù)區(qū)域 jdk 1.8之前與之后的內(nèi)存模型有差異,方法區(qū)有變化(https://cloud.tencent...
    陳晨_軟件五千言閱讀 1,387評(píng)論 2 12
  • 本文所有內(nèi)容來于:http://stuq.com/a/100ww java代碼是如何執(zhí)行的 java代碼是運(yùn)行于j...
    良辰美景TT閱讀 2,796評(píng)論 0 89

友情鏈接更多精彩內(nèi)容