簡書 占小狼 轉(zhuǎn)載請注明原創(chuàng)出處,謝謝衙耕!
大家新年好瀑凝,愿你們在新的一年順利晉升、工資漲漲漲...
之前無意間碰到一個(gè)有趣的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)象
程序運(yùn)行之后渴杆,執(zhí)行jstat -gcutils pid 1000
命令寥枝,結(jié)果如下:
在JVM參數(shù)中已經(jīng)設(shè)置了-XX:+UseCMSInitiatingOccupancyOnly
和 -XX:CMSInitiatingOccupancyFraction=75
只有在老年代的使用率達(dá)到75%時(shí)才會觸發(fā)CMS回收宪塔,可目前的現(xiàn)象是老年代使用率才60%,就開始不停的GC囊拜、不停的GC某筐、不停的GC,GC日志如下:
看這架勢冠跷,應(yīng)該是在不停的發(fā)生CMS GC了南誊。
原因查找
既然一直在觸發(fā)CMS,那問題根本因?yàn)樵谟|發(fā)CMS的條件中蜜托,之前以為只要設(shè)置了-XX:+UseCMSInitiatingOccupancyOnly
參數(shù)抄囚,只有在老年代的使用率達(dá)到閾值時(shí)才會觸發(fā)。
翻了代碼之后才發(fā)現(xiàn)橄务,問題沒這么簡單幔托,觸發(fā)CMS的判斷邏輯位于CMSCollector::shouldConcurrentCollect()
方法中,實(shí)現(xiàn)如下:
在設(shè)置了-XX:+UseCMSInitiatingOccupancyOnly
參數(shù)的前提下蜂挪,有三種情況會觸發(fā):
1重挑、老年代當(dāng)前使用率是否達(dá)到閾值CMSInitiatingOccupancyFraction
;
2、判斷當(dāng)前新生代的對象是否能夠全部順利的晉升到老年代棠涮,如果不能谬哀,就提早觸發(fā)一次老年代的收集,這是本案例中不停CMS的根本原因严肪,incremental_collection_will_fail(true)
實(shí)現(xiàn)如下:
其中get_gen(0)
指向當(dāng)前年輕代的堆玻粪,因?yàn)樵O(shè)置了-XX:+UseParNewGC
,則年輕代的堆實(shí)現(xiàn)是ParNewGeneration
诬垂,該類繼承了DefNewGeneration
劲室,方法collection_attempt_is_safe()
位于DefNewGeneration
類中,實(shí)現(xiàn)如下:
前面2個(gè)條件先忽略结窘,看最后一個(gè)條件很洋,_next_gen
指向老年代的堆,其中promotion_attempt_is_safe()
實(shí)現(xiàn)如下:
傳入的參數(shù)max_promotion_in_bytes
隧枫,由年輕代的used
方法計(jì)算得到喉磁,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時(shí)晉升到老年代對象大小的平均值
當(dāng)老年代的可用內(nèi)存大于av_promo
协怒,或者大于max_promotion_in_bytes
時(shí),說明下次的YGC是安全的卑笨,否則返回fasle孕暇,提早進(jìn)行一次CMS操作,釋放老年代的空間,以容納下次YGC晉升上來的對象妖滔。
到這里隧哮,本文中的例子不斷的進(jìn)行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)存使用率達(dá)到了閾值CMSInitiatingPermOccupancyFraction
,默認(rèn)值是92曲秉。
即使?jié)M足上面2個(gè)條件采蚀,還需要一層判斷update_should_unload_classes()
如果一開始永久代大小沒有設(shè)置、或者設(shè)置的很小承二,很有可能一開始就執(zhí)行CMS榆鼠,這讓很多同學(xué)表示懷疑,什么都沒做矢洲,就給我來一次CMS的日志。