一個有意思的CMS問題

簡書 占小狼 轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!

大家新年好,愿你們在新的一年順利晉升、工資漲漲漲...

之前無意間碰到一個有趣的CMS GC問題,問題很簡單,現(xiàn)象很粗暴。

代碼

/**
 * -Xmx20m -Xms20m -Xmn10m -XX:+UseParNewGC  -XX:+UseConcMarkSweepGC
 * -XX:+UseCMSInitiatingOccupancyOnly  -XX:CMSInitiatingOccupancyFraction=75
 * -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
 */
public class JVM {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {

        byte[] b1 = new byte[2 * _1MB];
        byte[] b2 = new byte[2 * _1MB];
        byte[] b3 = new byte[2 * _1MB];
        byte[] b4 = new byte[4 * _1MB];
        System.in.read();
    }
}

現(xiàn)象

程序運行之后,執(zhí)行jstat -gcutils pid 1000命令,結(jié)果如下:

在JVM參數(shù)中已經(jīng)設(shè)置了-XX:+UseCMSInitiatingOccupancyOnly-XX:CMSInitiatingOccupancyFraction=75

只有在老年代的使用率達到75%時才會觸發(fā)CMS回收,可目前的現(xiàn)象是老年代使用率才60%,就開始不停的GC、不停的GC、不停的GC,GC日志如下:

看這架勢,應(yīng)該是在不停的發(fā)生CMS GC了。

原因查找

既然一直在觸發(fā)CMS,那問題根本因為在觸發(fā)CMS的條件中,之前以為只要設(shè)置了-XX:+UseCMSInitiatingOccupancyOnly參數(shù),只有在老年代的使用率達到閾值時才會觸發(fā)。

翻了代碼之后才發(fā)現(xiàn),問題沒這么簡單,觸發(fā)CMS的判斷邏輯位于CMSCollector::shouldConcurrentCollect()方法中,實現(xiàn)如下:

在設(shè)置了-XX:+UseCMSInitiatingOccupancyOnly參數(shù)的前提下,有三種情況會觸發(fā):
1、老年代當(dāng)前使用率是否達到閾值CMSInitiatingOccupancyFraction;
2、判斷當(dāng)前新生代的對象是否能夠全部順利的晉升到老年代,如果不能,就提早觸發(fā)一次老年代的收集,這是本案例中不停CMS的根本原因,incremental_collection_will_fail(true)實現(xiàn)如下:

其中get_gen(0)指向當(dāng)前年輕代的堆,因為設(shè)置了-XX:+UseParNewGC,則年輕代的堆實現(xiàn)是ParNewGeneration,該類繼承了DefNewGeneration,方法collection_attempt_is_safe()位于DefNewGeneration類中,實現(xiàn)如下:

前面2個條件先忽略,看最后一個條件,_next_gen指向老年代的堆,其中promotion_attempt_is_safe()實現(xiàn)如下:

傳入的參數(shù)max_promotion_in_bytes,由年輕代的used方法計算得到,eden區(qū)的使用量 + from區(qū)的使用量

size_t DefNewGeneration::used() const {
  return eden()->used()
       + from()->used();      // to() is only used during scavenge
}

其中promotion_attempt_is_safe()方法中的變量
1、available是老年代的可用內(nèi)存大小
2、av_promo每次YGC時晉升到老年代對象大小的平均值

當(dāng)老年代的可用內(nèi)存大于av_promo,或者大于max_promotion_in_bytes時,說明下次的YGC是安全的,否則返回fasle,提早進行一次CMS操作,釋放老年代的空間,以容納下次YGC晉升上來的對象。

到這里,本文中的例子不斷的進行CMS GC的疑惑應(yīng)該可以解釋清楚了。

別忘了,還有第三種情況:

if (CMSClassUnloadingEnabled && _permGen->should_concurrent_collect()) {
    bool res = update_should_unload_classes();
    if (res) {
      if (Verbose && PrintGCDetails) {
        gclog_or_tty->print_cr("CMS perm gen initiated");
      }
      return true;
    }
  }

前提是設(shè)置了-XX:+CMSClassUnloadingEnabled,而且_permGen永久帶的內(nèi)存使用率達到了閾值CMSInitiatingPermOccupancyFraction,默認值是92。

即使?jié)M足上面2個條件,還需要一層判斷update_should_unload_classes()

如果一開始永久代大小沒有設(shè)置、或者設(shè)置的很小,很有可能一開始就執(zhí)行CMS,這讓很多同學(xué)表示懷疑,什么都沒做,就給我來一次CMS的日志。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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