成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

由「Metaspace容量不足觸發(fā)CMS GC」從而引發(fā)的思考

StonePanda / 1596人閱讀

摘要:第一個(gè)大陡坡是應(yīng)用發(fā)布,老年代內(nèi)存占比下降,很正常。但此時(shí)老年代內(nèi)存使用占比。因?yàn)楹笃诓⒉粫?huì)引發(fā)??梢钥闯?,由于到達(dá)時(shí)候,觸發(fā)了一次和一次。但觸發(fā)時(shí),占比并沒用明顯的規(guī)律。得出,擴(kuò)容導(dǎo)致這個(gè)說法,其實(shí)是不準(zhǔn)確的。

轉(zhuǎn)載請(qǐng)注明原文鏈接:https://www.jianshu.com/p/468...

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

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

之后毛老師發(fā)來一張圖。

圖片展示了老年代內(nèi)存占用情況。

第一個(gè)大陡坡是應(yīng)用發(fā)布,老年代內(nèi)存占比下降,很正常。

第二個(gè)小陡坡,老年代內(nèi)存占用突然下降,應(yīng)該是發(fā)生了老年代 GC。

但奇怪的是,此時(shí)老年代內(nèi)存占用并不高,發(fā)生 GC 并不是正?,F(xiàn)象。

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

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

但此時(shí)老年代內(nèi)存使用占比 = 234011K / 2621440k ≈ 9%。

而 CMS 觸發(fā)的條件是:

老年代內(nèi)存使用占比達(dá)到 CMSInitiatingOccupancyFraction,默認(rèn)為 92%,

毛老師設(shè)置的是 75%。

-XX:CMSInitiatingOccupancyFraction = 75

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

接著分析內(nèi)存狀況。

毛老師發(fā)現(xiàn)在老年代發(fā)生 GC 時(shí),Metaspace 的內(nèi)存占用也一起下降。

于是懷疑是 Metaspace 占用達(dá)到了設(shè)置的參數(shù) MetaspaceSize,發(fā)生了 GC。

查看 JVM 參數(shù)設(shè)置,MetaspaceSize 參數(shù)被設(shè)置為128m。

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

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

毛老師查看另外一個(gè)監(jiān)控工具,發(fā)生小陡坡的縱坐標(biāo)的確接近 128m。

此時(shí),引發(fā)出另一個(gè)問題:

Metaspace 發(fā)生 GC,為何會(huì)引起老年代 GC。

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

其中有幾個(gè)關(guān)鍵點(diǎn):

Metaspace 在空間不足時(shí),會(huì)進(jìn)行擴(kuò)容,并逐漸達(dá)到設(shè)置的 MetaspaceSize。

Metaspace 擴(kuò)容到 -XX:MetaspaceSize 參數(shù)指定的量,就會(huì)發(fā)生 FGC。

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

如果 Old 區(qū)配置 CMS 垃圾回收,那么擴(kuò)容引起的 FGC 也會(huì)使用 CMS 算法進(jìn)行回收。

其中的關(guān)鍵點(diǎn)是:

如果老年代設(shè)置了 CMS,則 Metasapce 擴(kuò)容引起的 FGC 會(huì)轉(zhuǎn)變成一次 CMS。

查看毛老師配置的 JVM 參數(shù),果然設(shè)置了 CMS GC。

-XX:+UseConcMarkSweepGC

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

從監(jiān)控來看,設(shè)置 -XX:MaxMetaspaceSize = 256m 已經(jīng)足夠。

因?yàn)楹笃诓⒉粫?huì)引發(fā) CMS GC。

GC 的問題算是解決了,但同時(shí)引發(fā)了以下幾點(diǎn)思考:

Metaspace 分配和擴(kuò)容有什么規(guī)律?

JDK 1.8 中的 Metaspace 和 JDK 1.7 中的 Perm 區(qū)有什么區(qū)別?

老年代回收設(shè)置成非 CMS 時(shí),Metaspace 占用到達(dá) -XX:MetaspaceSize 會(huì)引發(fā)什么 GC?

如何制造 Metasapce 內(nèi)存占用上升?

關(guān)于這個(gè)問題一和問題二,阿飛Javaer 已經(jīng)解釋的比較清楚。

對(duì)于 Metaspce,其初始大小并不等于設(shè)置的 -XX:MetaspaceSize 參數(shù)。

隨著類的加載,Metaspce 會(huì)不斷進(jìn)行擴(kuò)容,直到達(dá)到 -XX:MetaspaceSize 觸發(fā) GC。

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

在 openjdk 的 bug 列表中,找到一個(gè) 關(guān)于 Metaspace 初始大小的 bug,并且尚未解決。

對(duì)于問題二, 阿飛Javaer 在文章中也進(jìn)行了說明。

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

JVM 在啟動(dòng)的時(shí)候會(huì)根據(jù) -XX:PermSize 初始化分配一塊連續(xù)的內(nèi)存塊。

這樣的話,如果 -XX:PermSize 設(shè)置過大,就是一種赤果果的浪費(fèi)。

關(guān)于 Metaspace,JVM 還提供了其余一些設(shè)置參數(shù)。

可以通過以下命令查看。

java -XX:+PrintFlagsFinal -version | grep Metaspace

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

問題三

Metaspace 占用到達(dá) -XX:MetaspaceSize 會(huì)引發(fā)什么?

已經(jīng)知道,當(dāng)老年代回收設(shè)置成 CMS GC 時(shí),會(huì)觸發(fā)一次 CMS GC。

那么如果不設(shè)置為 CMS GC,又會(huì)發(fā)生什么呢?

使用以下配置進(jìn)行一個(gè)小嘗試,然后查看 GC log。

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

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

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

于是,設(shè)置 -XX:MetaspaceSize = 40m,期望發(fā)生一次 GC。

從 GC log 中,可以找到以下關(guān)鍵日志。

[GC (Metadata GC Threshold) 
[PSYoungGen: 360403K->47455K(917504K)] 360531K->47591K(1966080K), 0.0343563 secs] 
[Times: user=0.08 sys=0.00, real=0.04 secs] 

[Full GC (Metadata GC Threshold) 
[PSYoungGen: 47455K->0K(917504K)] 
[ParOldGen: 136K->46676K(1048576K)] 47591K->46676K(1966080K), 
[Metaspace: 40381K->40381K(1085440K)], 0.1712704 secs] 
[Times: user=0.42 sys=0.02, real=0.17 secs] 

可以看出,由于 Metasapce 到達(dá) -XX:MetaspaceSize = 40m 時(shí)候,觸發(fā)了一次 YGC 和一次 Full GC。

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

所以一般都會(huì)直描述,當(dāng) Metasapce 到達(dá) -XX:MetaspaceSize 時(shí)會(huì)觸發(fā)一次 Full GC。

問題四

如何人工模擬 Metaspace 內(nèi)存占用上升?

Metaspace 是 JDK 1.8 之后引入的一個(gè)區(qū)域。

有一點(diǎn)可以肯定的,Metaspace 會(huì)保存類的描述信息。

JVM 需要根據(jù) Metaspace 中的信息,才能找到堆中類 java.lang.Class 所對(duì)應(yīng)的對(duì)象。(有點(diǎn)繞)

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

于是想到,使用 CGlib 動(dòng)態(tài)代理,生成被代理類的子類。

簡單的 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();
        // 設(shè)置需要?jiǎng)?chuàng)建子類的類
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        enhancer.setUseCache(false);
        // 通過字節(jié)碼技術(shù)動(dòng)態(tài)創(chuàng)建子類實(shí)例
        return enhancer.create();
    }

    // 實(shí)現(xiàn)MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("前置代理");
        // 通過代理類調(diào)用父類中的方法
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("后置代理");
        return result;
    }
}

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

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

應(yīng)用啟動(dòng)完畢后,請(qǐng)求 /getProxy 接口,發(fā)現(xiàn) Metaspace 空間占用上升。

從堆 Dump 中也可以發(fā)現(xiàn),有很多被 CGlib 所代理的 SayHello 類對(duì)象。

代理類對(duì)應(yīng)的 java.lang.Class 對(duì)象分配在堆內(nèi),類的描述信息在 Metaspace 中。

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

最后,當(dāng) Metaspace 使用空間超過設(shè)置的 -XX:MaxMetaspaceSize=128m 時(shí),就會(huì)發(fā)生 OOM。

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

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

但會(huì)出現(xiàn)以下字樣。

Full GC (Last ditch collection)

此外,還有一個(gè)問題。

當(dāng) Metaspace 內(nèi)存占用達(dá)到 -XX:MetaspaceSize 時(shí),Metaspace 只擴(kuò)容,不會(huì)引起 Full GC。

當(dāng) Metaspace 內(nèi)存占用達(dá)到 -XX:MetaspaceSize 時(shí),會(huì)發(fā)生 Full GC。

在發(fā)生第一次 Full GC 之后,Metaspace 依然會(huì)擴(kuò)容。

那么,第二次觸發(fā) Full GC 的條件是?

有文章說,在觸發(fā)第一次F Full GC 后,之后 Metaspace 的每次擴(kuò)容,都會(huì)引起 Full GC。

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

在第一次 Full GC 之后,之后 Metaspace 的擴(kuò)容,并不一定會(huì)引起 Full GC。

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

jstat FGC 次數(shù)一直都是 1。

此外,使用 GClib 動(dòng)態(tài)生成類,Metaspace 繼續(xù)擴(kuò)容,到一定程度,觸發(fā)了 Full GC。

但觸發(fā) FGC 時(shí),Metaspace 占比并沒用明顯的規(guī)律。

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

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

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

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

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

文章有一句話:

從上述分析中可以發(fā)現(xiàn),gc操作的入口都位于GenCollectedHeap::do_collection方法中。
不同的參數(shù)執(zhí)行不同類型的gc。

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

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

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

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

得出,YGC 和 Full GC 的確會(huì)重新計(jì)算 Metaspace 的大小。

至于是否進(jìn)行擴(kuò)容和縮容,則需要根據(jù) compute_new_size() 方法的計(jì)算結(jié)果而定。

得出,Metasapce 擴(kuò)容導(dǎo)致 GC 這個(gè)說法,其實(shí)是不準(zhǔn)確的。

正確的過程是:新建類導(dǎo)致 Metaspace 容量不夠,觸發(fā) GC,GC 完成后重新計(jì)算 Metaspace 新容量,決定是否對(duì) Metaspace 擴(kuò)容或縮容。

參考資料

JVM參數(shù)MetaspaceSize的誤解 https://www.jianshu.com/p/b44...

JVM源碼分析之垃圾收集的執(zhí)行過程 https://www.jianshu.com/p/04e...

JVM源碼分析之Metaspace解密 http://lovestblog.cn/blog/201...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76765.html

相關(guān)文章

  • jvm內(nèi)存分配策略和性能監(jiān)控

    摘要:概述本篇旨在講清楚的內(nèi)存分配策略,日志閱讀,一些常見名詞和提供的一些性能監(jiān)控工具。內(nèi)存分配與回收策略對(duì)象優(yōu)先在分配大多數(shù)情況下,對(duì)象優(yōu)先在新生代區(qū)中分配。當(dāng)區(qū)域沒有足夠空間進(jìn)行分配時(shí),將發(fā)生一次。 概述 本篇旨在講清楚jvm的內(nèi)存分配策略,gc日志閱讀,一些常見名詞和jdk提供的一些性能監(jiān)控工具。廢話不多說,開始上貨。 GC日志閱讀 在開發(fā)的世界里,閱讀日志是最基礎(chǔ)的能力,也是解決問題...

    Baoyuan 評(píng)論0 收藏0
  • 學(xué)習(xí)JVM是如何從入門到放棄?

    摘要:而字節(jié)碼運(yùn)行在之上,所以不用關(guān)心字節(jié)碼是在哪個(gè)操作系統(tǒng)編譯的,只要符合規(guī)范,那么,這個(gè)字節(jié)碼文件就是可運(yùn)行的。好處防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼安全性角度特別說明類加載器在成功加載某個(gè)類之后,會(huì)把得到的類的實(shí)例緩存起來。 前言 只有光頭才能變強(qiáng) JVM在準(zhǔn)備面試的時(shí)候就有看了,一直沒時(shí)間寫筆記。現(xiàn)在到了一家公司實(shí)習(xí),閑的時(shí)候就寫寫,刷刷JVM博客,刷刷電子書。 學(xué)習(xí)JVM的目的也很簡單...

    Joyven 評(píng)論0 收藏0
  • 系統(tǒng)優(yōu)化怎么做-Tomcat優(yōu)化

    摘要:運(yùn)行模式分種模式一般使用模式效率低對(duì)系統(tǒng)配置有一些比較高的要求確認(rèn)的運(yùn)行模式配置文件關(guān)鍵配置最大線程數(shù)默認(rèn)是最小活躍線程數(shù)默認(rèn)是最大的等待隊(duì)列個(gè)數(shù),超過則請(qǐng)求拒絕默認(rèn)值是,一般不改變。 前言 Tomcat作為Web應(yīng)用的服務(wù)器,目前絕大多數(shù)公司都是用其作為應(yīng)用服務(wù)器的,應(yīng)用服務(wù)器的執(zhí)行效率會(huì)影響系統(tǒng)執(zhí)行,這里會(huì)講Tomcat怎樣進(jìn)行配置能提高處理性能。另外必須提到對(duì)應(yīng)的JVM參數(shù)的優(yōu)化...

    gghyoo 評(píng)論0 收藏0
  • JVM 完整深入解析

    摘要:堆內(nèi)存的劃分在里面的示意圖垃圾回收一判斷對(duì)象是否要回收的方法可達(dá)性分析法可達(dá)性分析法通過一系列對(duì)象作為起點(diǎn)進(jìn)行搜索,如果在和一個(gè)對(duì)象之間沒有可達(dá)路徑,則稱該對(duì)象是不可達(dá)的。 工作之余,想總結(jié)一下JVM相關(guān)知識(shí)。 Java運(yùn)行時(shí)數(shù)據(jù)區(qū): Java虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)將其管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域,這些區(qū)域有各自的用途、創(chuàng)建和銷毀的時(shí)間,有些區(qū)域隨虛擬機(jī)進(jìn)程的啟動(dòng)而...

    shenhualong 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

StonePanda

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<