由「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」从而引发的思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PS里最方便的调色工具,但要掌握这个要点
- 下一篇: 一次堆外内存泄露的排查过程