JVM

JVM-09丨触发 CMS GC 的条件

Posted by jiefang on October 28, 2019

触发 CMS GC 的条件

CMS GC 在实现上分成 foreground collector 和 background collector。

foreground collector

foreground collector 触发条件比较简单,一般是遇到对象分配但空间不够,就会直接触发 GC,来立即进行空间回收。采用的算法是 mark sweep,不压缩。

background collector

background collector 的流程,它是通过 CMS 后台线程不断的去扫描,过程中主要是判断是否符合 background collector 的触发条件,一旦有符合的情况,就会进行一次 background 的 collect。 每次扫描过程中,先等 CMSWaitDuration 时间,然后再去进行一次 shouldConcurrentCollect 判断,看是否满足 CMS background collector 的触发条件。CMSWaitDuration 默认时间是 2s(经常会有业务遇到频繁的 CMS GC,注意看每次 CMS GC 之间的时间间隔,如果是 2s,那基本就可以断定是 CMS 的 background collector)。

1.是否是并行 Full GC

指的是在 GC cause 是 gclocker 且配置了 GCLockerInvokesConcurrent 参数, 或者 GC cause 是java.lang.system::gc(就是 System.gc()调用)and 且配置了 ExplicitGCInvokesConcurrent 参数,这时会触发一次 background collector

2.根据统计数据动态计算

未配置 UseCMSInitiatingOccupancyOnly时,会根据统计数据动态判断是否需要进行一次 CMS GC。判断逻辑是,如果预测 CMS GC完成所需要的时间大于预计的老年代将要填满的时间,则进行 GC。这些判断是需要基于历史的 CMS GC 统计指标,然而,第一次 CMS GC 时,统计数据还没有形成,是无效的,这时会跟据 Old Gen 的使用占比来判断是否要进行 GC。 那占多少比率,开始回收呢?(也就是 bootstrapoccupancy 的值是多少呢?)答案是 50%。或许你已经遇到过类似案例,在没有配置 UseCMSInitiatingOccupancyOnly 时,发现老年代占比到 50% 就进行了一次 CMS GC,当时的你或许还一头雾水呢。

3.根据 Old Gen 情况判断

主要分成两类:

(a) Old Gen 空间使用占比情况与阈值比较,如果大于阈值则进行CMS GC也就是”occupancy() > initiatingoccupancy()”,occupancy 毫无疑问是Old Gen 当前空间的使用占比,而 initiatingoccupancy 是多少呢?

1
2
3
4
5
6
7
8
9
10
11
 _cmsGen ->init_initiating_occupancy(CMSInitiatingOccupancyFraction,CMSTriggerRatio);
...
voidConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr){
    assert(io <=100&& tr <=100,"Check the arguments");
    if(io >=0){
        _initiating_occupancy =(double)io /100.0;
    }else{
        _initiating_occupancy =((100-MinHeapFreeRatio)+
        (double)(tr *MinHeapFreeRatio)/100.0)/100.0;
    }
}

可以看到当 CMSInitiatingOccupancyFraction 参数配置值大于 0,就是 “io / 100.0”;当 CMSInitiatingOccupancyFraction 参数配置值小于 0 时(注意,默认是 -1),是 “((100 - MinHeapFreeRatio) + (double)(tr * MinHeapFreeRatio) / 100.0) / 100.0”,这到底是多少呢?是 92%,这里就不贴出具体的计算过程了,或许你已经在某些书或者博客中了解过,CMSInitiatingOccupancyFraction 没有配置,就是 92,但是其实 CMSInitiatingOccupancyFraction 没有配置是 -1,所以阈值取后者 92%,并不是 CMSInitiatingOccupancyFraction 的值是 92。

4.根据增量 GC 是否可能会失败(悲观策略)

5.根据 meta space 情况判断

这里主要看 metaspace 的 shouldconcurrent_collect 标志,这个标志在 meta space 进行扩容前如果配置了 CMSClassUnloadingEnabled 参数时,会进行设置。这种情况下就会进行一次 CMS GC。因此经常会有应用启动不久,Old Gen 空间占比还很小的情况下,进行了一次 CMS GC,让你很莫名其妙,其实就是这个原因导致的。