专栏名称: 占小狼
如果读完觉得有收获的话,欢迎点赞加关注 微信公众号 占小狼的博客 http://upload-images.jianshu.io/upload_images/2184951-2079ac376dbc9c0c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 推荐一个干货社区 Spring For All 社区( http://spring4all.com )
目录
相关文章推荐
株洲交通频道广播电台  ·  售罄、约满、限流!多地景区紧急提醒→ ·  2 天前  
株洲交通频道广播电台  ·  时间定了!油价要变 ·  4 天前  
鲁中晨报  ·  -9℃+雨夹雪!淄博未来几天…… ·  6 天前  
51好读  ›  专栏  ›  占小狼

一个有意思的频繁CMS问题

占小狼  · 简书  ·  · 2017-10-12 20:28

正文

简书 占小狼
转载请注明原创出处,谢谢!

无意间碰到一个有趣的问题,《JVM菜鸟进阶高手之路十三(等你来战!!!)》 ,问题很简单,现象很粗暴。

代码

/**
 * -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();
    }
}

现象

程序运行之后,执行jstat -gcutils pid 1000命令,结果如下:

在JVM参数中已经设置了-XX:+UseCMSInitiatingOccupancyOnly-XX:CMSInitiatingOccupancyFraction=75

只有在老年代的使用率达到75%时才会触发CMS回收,可目前的现象是老年代使用率才60%,就开始不停的GC、不停的GC、不停的GC,GC日志如下:

看样子是一直在发生CMS GC

原因

既然一直在触发CMS,那问题根本因为在触发CMS的条件中,之前以为只要设置了-XX:+UseCMSInitiatingOccupancyOnly参数,只有在老年代的使用率达到阈值时才会触发,其实触发条件还有其它两个。

是否触发CMS的判断位于CMSCollector::shouldConcurrentCollect()方法中,实现如下:

在设置了-XX:+UseCMSInitiatingOccupancyOnly参数的前提下,有三种情况会触发:
1、_cmsGen->should_concurrent_collect(),不用想了,这个就是判断当前老年代使用率是否达到阈值XX:CMSInitiatingOccupancyFraction;
2、gch->incremental_collection_will_fail(true) 这是代码不停CMS的根本原因,判断当前新生代的对象是否能够全部顺利的晋升到老年代,如果不能,就提早触发一次CMS。

incremental_collection_will_fail(true)实现如下:

其中get_gen(0)指向当前年轻代的堆,因为设置了-XX:+UseParNewGC,则年轻代的堆实现是ParNewGeneration,该类继承了DefNewGeneration,方法collection_attempt_is_safe()位于DefNewGeneration类中,实现如下:

前面2个条件其实不用管,极少情况会出现,主要看最后一个条件,_next_gen指向老年代的堆,其中promotion_attempt_is_safe()实现如下:

传入的参数max_promotion_in_bytes,由年轻代的used方法计算得到,eden区的使用量 + from区的使用量

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

其中promotion_attempt_is_safe()方法中的变量
1、available是老年代的可用内存大小
2、av_promo每次YGC时晋升到老年代对象大小的平均值

当老年代的可用内存大于av_promo,或者大于max_promotion_in_bytes时,说明下次的YGC是安全的,否则返回fasle,提早进行一次CMS操作,释放老年代的空间,以容纳下次YGC晋升上来的对象。

到这里,本文中的例子不断的进行CMS GC的疑惑应该可以解释清楚了。

别忘了,还有第三种情况:

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;
    }
  }

前提是设置了-XX:+CMSClassUnloadingEnabled,而且_permGen永久带的内存使用率达到了阈值CMSInitiatingPermOccupancyFraction,默认值是92。

即使满足上面2个条件,还需要一层判断update_should_unload_classes()

  1. 如果有类似System.gc()动作,且设置了ExplicitGCInvokesConcurrentAndUnloadsClasses参数,可以执行CMS。
  2. 如果设置了参数-XX:+CMSClassUnloadingEnabled,且只要满足以下3个条件中的一个即可。