日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

由「Metaspace容量不足触发CMS GC」从而引发的思考

發布時間:2023/12/3 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 由「Metaspace容量不足触发CMS GC」从而引发的思考 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自??由「Metaspace容量不足觸發CMS GC」從而引發的思考

某天早上,毛老師在群里問「cat 上怎么看 gc」。

?

好好的一個群

看到有 GC 的問題,立馬做出小雞搓手狀。


之后毛老師發來一張圖。

?

老年代內存占用情況

圖片展示了老年代內存占用情況。

第一個大陡坡是應用發布,老年代內存占比下降,很正常。

第二個小陡坡,老年代內存占用突然下降,應該是發生了老年代 GC。

但奇怪的是,此時老年代內存占用并不高,發生 GC 并不是正常現象。

于是,毛老師查看了 GC log。

?

GC log

從 GC log 中可以看出,老年代發生了一次 CMS GC。

但此時老年代內存使用占比 = 234011K / 2621440k ≈ 9%。

而 CMS 觸發的條件是:

老年代內存使用占比達到?CMSInitiatingOccupancyFraction,默認為 92%,

毛老師設置的是 75%。

1-XX:CMSInitiatingOccupancyFraction?=?75

于是排除老年代占用過高的可能。

接著分析內存狀況。

?

Metaspace 內存占用情況

毛老師發現在老年代發生 GC 時,Metaspace 的內存占用也一起下降。

于是懷疑是 Metaspace 占用達到了設置的參數 MetaspaceSize,發生了 GC。

查看 JVM 參數設置,MetaspaceSize 參數被設置為128m。

1-XX:MetaspaceSize?=?128m?-XX:MaxMetaspaceSize?=?256m

問題的原因被集中在 Metaspace 上。

毛老師查看另外一個監控工具,發生小陡坡的縱坐標的確接近 128m。

此時,引發出另一個問題:

Metaspace 發生 GC,為何會引起老年代 GC。

于是,想到之前看過?阿飛Javaer?的文章 《JVM參數MetaspaceSize的誤解》。

其中有幾個關鍵點:

Metaspace 在空間不足時,會進行擴容,并逐漸達到設置的 MetaspaceSize。

Metaspace 擴容到 -XX:MetaspaceSize 參數指定的量,就會發生 FGC。

如果配置了 -XX:MetaspaceSize,那么觸發 FGC 的閾值就是配置的值。

如果 Old 區配置 CMS 垃圾回收,那么擴容引起的 FGC 也會使用 CMS 算法進行回收。

其中的關鍵點是:

如果老年代設置了 CMS,則 Metasapce 擴容引起的 FGC 會轉變成一次 CMS。

查看毛老師配置的 JVM 參數,果然設置了 CMS GC。

1-XX:+UseConcMarkSweepGC

于是,解決問題的方法是調整 -XX:MetaspaceSize = 256m。

從監控來看,設置 -XX:MaxMetaspaceSize = 256m 已經足夠。

因為后期并不會引發 CMS GC。


GC 的問題算是解決了,但同時引發了以下幾點思考:

  • Metaspace 分配和擴容有什么規律?

  • JDK 1.8 中的 Metaspace 和 JDK 1.7 中的 Perm 區有什么區別?

  • 老年代回收設置成非 CMS 時,Metaspace 占用到達 -XX:MetaspaceSize 會引發什么 GC?

  • 如何制造 Metasapce 內存占用上升?

  • 關于這個問題一和問題二,阿飛Javaer?已經解釋的比較清楚。

    對于 Metaspce,其初始大小并不等于設置的 -XX:MetaspaceSize 參數。

    隨著類的加載,Metaspce 會不斷進行擴容,直到達到 -XX:MetaspaceSize 觸發 GC。

    而至于如何設置 Metaspace 的初始大小,目前的確沒有辦法。

    在 openjdk 的 bug 列表中,找到一個?關于 Metaspace 初始大小的 bug,并且尚未解決。

    ?

    Add JVM option to set initial Metaspace size

    對于問題二,?阿飛Javaer?在文章中也進行了說明。

    Perm 的話,我們通過配置 -XX:PermSize 以及 -XX:MaxPermSize 來控制這塊內存的大小。

    JVM 在啟動的時候會根據 -XX:PermSize 初始化分配一塊連續的內存塊。

    這樣的話,如果 -XX:PermSize 設置過大,就是一種赤果果的浪費。

    關于 Metaspace,JVM 還提供了其余一些設置參數。

    可以通過以下命令查看。

    1java?-XX:+PrintFlagsFinal?-version?|?grep?Metaspace

    關于 Metaspace 更多的內容,可以參考笨神的文章:《JVM源碼分析之Metaspace解密》。

    問題三

    Metaspace 占用到達 -XX:MetaspaceSize 會引發什么?

    已經知道,當老年代回收設置成 CMS GC 時,會觸發一次 CMS GC。

    那么如果不設置為 CMS GC,又會發生什么呢?

    使用以下配置進行一個小嘗試,然后查看 GC log。

    1-Xmx2048m?-Xms2048m?-Xmn1024m?
    2-XX:MetaspaceSize=40m?-XX:MaxMetaspaceSize=128m
    3-XX:+PrintGCDetails?-XX:+PrintGCDateStamps?
    4-XX:+PrintHeapAtGC?-Xloggc:d:/heap_trace.txt

    該配置并未設置 CMS GC,JDK 1.8 默認的老年代回收算法為 ParOldGen。

    本文測試的應用在啟動完成后,占用 Metaspace 空間約為 63m,可通過 jstat 命令查看。

    于是,設置 -XX:MetaspaceSize = 40m,期望發生一次 GC。

    從 GC log 中,可以找到以下關鍵日志。

    1[GC?(Metadata?GC?Threshold)?
    2[PSYoungGen:?360403K->47455K(917504K)]?360531K->47591K(1966080K),?0.0343563?secs]?
    3[Times:?user=0.08?sys=0.00,?real=0.04?secs]?
    4
    5[Full?GC?(Metadata?GC?Threshold)?
    6[PSYoungGen:?47455K->0K(917504K)]?
    7[ParOldGen:?136K->46676K(1048576K)]?47591K->46676K(1966080K),?
    8[Metaspace:?40381K->40381K(1085440K)],?0.1712704?secs]?
    9[Times:?user=0.42?sys=0.02,?real=0.17?secs]?

    可以看出,由于 Metasapce 到達 -XX:MetaspaceSize = 40m 時候,觸發了一次 YGC 和一次 Full GC。

    一般而言,我們對 Full GC 的重視度比對 YGC 高很多。

    所以一般都會直描述,當 Metasapce 到達 -XX:MetaspaceSize 時會觸發一次 Full GC。

    問題四

    如何人工模擬 Metaspace 內存占用上升?

    Metaspace 是 JDK 1.8 之后引入的一個區域。

    有一點可以肯定的,Metaspace 會保存類的描述信息。

    JVM 需要根據 Metaspace 中的信息,才能找到堆中類 java.lang.Class 所對應的對象。(有點繞)

    既然 Metaspace 中會保存類描述信息,可以通過新建類來增加 Metaspace 的占用。

    于是想到,使用 CGlib 動態代理,生成被代理類的子類。

    簡單的 SayHello 類。

    public?class?SayHello?{public?void?say()?{System.out.println("hello?everyone");} }

    簡單的代理類,使用 CGlib 生成子類。

    public?class?CglibProxy?implements?MethodInterceptor?{public?Object?getProxy(Class?clazz)?{Enhancer?enhancer?=?new?Enhancer();//?設置需要創建子類的類enhancer.setSuperclass(clazz);enhancer.setCallback(this);enhancer.setUseCache(false);//?通過字節碼技術動態創建子類實例return?enhancer.create();}//?實現MethodInterceptor接口方法public?Object?intercept(Object?obj,?Method?method,?Object[]?args,?MethodProxy?proxy)?throws?Throwable?{System.out.println("前置代理");//?通過代理類調用父類中的方法Object?result?=?proxy.invokeSuper(obj,?args);System.out.println("后置代理");return?result;} }

    簡單新建一個 Controller 用于測試生成 10000 個 SayHello 子類。

    @RequestMapping(value?=?"/getProxy",?method?=?RequestMethod.GET) @ResponseBody public?void?getProxy()?{CglibProxy?proxy?=?new?CglibProxy();for?(int?i?=?0;?i?<?10000;?i++)?{//通過生成子類的方式創建代理類SayHello?proxyTmp?=?(SayHello)?proxy.getProxy(SayHello.class);proxyTmp.say();} }

    應用啟動完畢后,請求 /getProxy 接口,發現 Metaspace 空間占用上升。

    ?

    CGlib 動態代理生成子類

    從堆 Dump 中也可以發現,有很多被 CGlib 所代理的 SayHello 類對象。

    ?

    堆 Dump 分析

    代理類對應的 java.lang.Class 對象分配在堆內,類的描述信息在 Metaspace 中。

    堆中有多個 Class 對象,可以推斷出 Metasapce 需要裝下很多類描述信息。

    最后,當 Metaspace 使用空間超過設置的 -XX:MaxMetaspaceSize=128m 時,就會發生 OOM。

    1Exception?in?thread?"http-nio-8080-exec-6"?java.lang.OutOfMemoryError:?Metaspace

    從 GC log 中可以看到,JVM 會在 Metaspace 占用滿之后,嘗試 Full GC。

    但會出現以下字樣。

    1Full?GC?(Last?ditch?collection)


    此外,還有一個問題。

    當 Metaspace 內存占用達到 -XX:MetaspaceSize 時,Metaspace 只擴容,不會引起 Full GC。

    當 Metaspace 內存占用達到 -XX:MetaspaceSize 時,會發生 Full GC。

    在發生第一次 Full GC 之后,Metaspace 依然會擴容。

    那么,第二次觸發 Full GC 的條件是?

    有文章說,在觸發第一次F Full GC 后,之后 Metaspace 的每次擴容,都會引起 Full GC。

    但觀察本文測試的 GC log 和 jstat 命令查看 Metasapce 擴容狀況,可以看出:

    在第一次 Full GC 之后,之后 Metaspace 的擴容,并不一定會引起 Full GC。

    ?

    觸發一次 Full GC

    從 jstat 輸出可以看到,在觸發一次 Full GC 之后,Metaspace 依舊發生了擴容,但未發生 Full GC。

    jstat FGC 次數一直都是 1。

    此外,使用 GClib 動態生成類,Metaspace 繼續擴容,到一定程度,觸發了 Full GC。

    但觸發 FGC 時,Metaspace 占比并沒用明顯的規律。

    ?

    Metaspace 持續擴容再次觸發 FGC

    嘗試了幾次,由于 jstat 設置了 1s 鐘輸出一次,所以每次觸發 Full GC 時候,MC 的數據都不一樣,但基本是相同。

    猜測在第一次 Full GC 之后,之后再次觸發 Full GC 的閾值是有一定的計算公式的。

    但具體如何計算,估計是需要深入源碼了。


    此外可以看到,每次 Metaspace 擴容時,都伴隨著一次 YGC 或者 Full GC,不知道是否是巧合。

    接著看到?占小狼?的文章 《JVM源碼分析之垃圾收集的執行過程》。

    文章有一句話:

    從上述分析中可以發現,gc操作的入口都位于GenCollectedHeap::do_collection方法中。
    不同的參數執行不同類型的gc。

    打開 openjdk 8 中的 GenCollectedHeap 類,查看 do_collection 方法。

    可以看到,在 do_collection 方法中,有這個一段代碼。

    if?(complete)?{//?Delete?metaspaces?for?unloaded?class?loaders?and?clean?up?loader_data?graphClassLoaderDataGraph::purge();MetaspaceAux::verify_metrics();//?Resize?the?metaspace?capacity?after?full?collectionsMetaspaceGC::compute_new_size();update_full_collections_completed(); }

    其中最主要的是?MetaspaceGC::compute_new_size();。

    得出,YGC 和 Full GC 的確會重新計算 Metaspace 的大小。

    至于是否進行擴容和縮容,則需要根據?compute_new_size()?方法的計算結果而定。

    得出,Metasapce 擴容導致 GC 這個說法,其實是不準確的。

    正確的過程是:新建類導致 Metaspace 容量不夠,觸發 GC,GC 完成后重新計算 Metaspace 新容量,決定是否對 Metaspace 擴容或縮容。

    總結

    以上是生活随笔為你收集整理的由「Metaspace容量不足触发CMS GC」从而引发的思考的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。