CMS垃圾收集器小实验之CMSInitiatingOccupancyFraction参数

背景

测试CMSInitiatingOccupancyFraction参数,测试结果和我的预期不符,所以花了一点时间一探究竟,文中有一些细节问题搞得不是特别清楚,但是也解决了我的困惑,在此记录一下。

预备知识

CMS收集器

garbage-collection-algorithms-implementations#concurrent-mark-and-sweep

CMSInitiatingOccupancyFraction说明

触发cms gc的老年代占用率,比如设置-XX:CMSInitiatingOccupancyFraction=80,那么在老年代占用率达到80%时触发cms gc。

CMSWaitDuration说明

cms gc线程定时执行的时间间隔,默认为2s一次。

测试步骤

测试环境准备

1.下载fastdebug版本openjdk(debug版本的jdk可以输出调试信息,方便分析问题),下载地址fastdebug

2.准备测试代码

public static void log(String msg){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date())+" "+msg);
} public static void displayMemoryInfo(){
log("======memory info start=======");
MemoryMXBean mmbean = ManagementFactory.getMemoryMXBean();
List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
for(MemoryPoolMXBean bean : pools){
System.out.println(bean.getName()+"="+bean.getUsage());
}
log("======memory info end======="); } public static void testCMSInitiatingOccupancyFraction() throws Exception{
log("a1 allocate start ");
List<byte[]> list = new ArrayList<>(3);
byte a1[] = new byte[5*_1MB];//a1内存直接分配到eden区,此时eden区内存占用5m+
list.add(a1);
log(" a1 allocated,sleep 4s");
displayMemoryInfo();
Thread.sleep(4000); log("a2 allocate start ");
byte a2[] = new byte[5*_1MB];//由于a1占用了eden 5m+内存,剩余内存不足以容纳a2,
//此时会触发ygc,因为s0和s1都不足以容纳a1,所以a1直接晋升到老年代,
//然后将a2分配到年轻代,老年代当前内存占用5m+,年轻代当前内存占用5m+
list.add(a2);
log(" a2 allocated,sleep 4s");
displayMemoryInfo();
Thread.sleep(4000); log("a3 allocate start ");
byte a3[] = new byte[10*_1MB];
list.add(a3);
log(" a3 allocated,exit it");//直接分配到老年代,老年代当前15M+
displayMemoryInfo();
Thread.sleep(10000);
System.exit(0);
}

3.整个堆30m,年轻代10m,eden 8m,so/s1 1m

4.测试代码搭配的jvm参数

java -verbose:gc -Xms30M -Xmx30M -Xmn10M -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps  -XX:SurvivorRatio=8 -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80  -XX:+UseConcMarkSweepGC -XX:+PrintTenuringDistribution  -XX:+PrintHeapAtGC  -XX:+PrintGC -XX:+PrintCMSInitiationStatistics -XX:CMSWaitDuration=3000 -XX:+Verbose coding4fun.jvm.TestCMS > testCMSInitiatingOccupancyFraction.log

我设置了CMSInitiatingOccupancyFraction=80,CMSWaitDuration=3000,意味着cms gc线程每3s执行一次,如果检测到老年代内存占用率达到80%,那么就触发垃圾收集。

预期分析

1.构造a1对象,a1是一个5m的字节数组;

2.睡眠4s,等待cms gc 线程执行;

3.cms gc线程执行,此时老年代空间利用率为0,不应该触发垃圾收集;

4.构造a2对象,a2是一个5m的字节数组,由于eden区空间不足,这时导致a1直接晋升到老年代,此时老年代内存占用5m+;

5.睡眠4s;

6.cms gc线程执行,此时老年代空间占用率为20%+,依然小于CMSInitiatingOccupancyFraction,不应该触发垃圾收集;

7.构造a3对象,a3是一个10m的字节数据,由于大于eden区空间,所以直接分配到老年代,此时老年代内存占用15m+;

8.睡眠10s;

9.cms gc线程执行,此时老年代空间利用率为75%+,依然小于CMSInitiatingOccupancyFraction,不应该触发垃圾收集;

以上是我结合理论知识对cms gc行为的一个预测,下一步我试图结合真实的gc log来验证结果是否符合预期。

通过gc日志验证猜想

gc日志比较长,我截取其中一部分:

2020-05-29 14:59:46 a1 allocate start
2020-05-29 14:59:46 a1 allocated,sleep 4s
###1 a1 分配完以后打印内存信息,此时eden区内存占用6980K,old区空着
2020-05-29 14:59:46 ======memory info start=======
Code Cache=init = 2555904(2496K) used = 2514048(2455K) committed = 2555904(2496K) max = 251658240(245760K)
Metaspace=init = 0(0K) used = 4624072(4515K) committed = 4980736(4864K) max = -1(-1K)
Compressed Class Space=init = 0(0K) used = 458088(447K) committed = 524288(512K) max = 1073741824(1048576K)
Par Eden Space=init = 8388608(8192K) used = 7148216(6980K) committed = 8388608(8192K) max = 8388608(8192K)
Par Survivor Space=init = 1048576(1024K) used = 0(0K) committed = 1048576(1024K) max = 1048576(1024K)
CMS Old Gen=init = 20971520(20480K) used = 0(0K) committed = 20971520(20480K) max = 20971520(20480K)
2020-05-29 14:59:46 ======memory info end======= ###2 cms gc 线程第一次执行(属于jvm的debug信息,使用fastdebug版本的jdk才能输出,这也是我选用fastdebug版本jdk做测试的原因),
###因为不满足触发条件所以没有触发cms收集过程
### openjdk 代码位置ConcurrentMarkSweepGeneration::promotion_attempt_is_safe
CMS: promo attempt is safe: available(20971520) >= av_promo(0),max_promo(7148216) 2020-05-29 14:59:50 a2 allocate start
TwoGenerationCollectorPolicy::mem_allocate_work: attempting locked slow path allocation
DefNewGeneration::allocate_from_space(655362): will_fail: false heap_lock: locked free: 1048576 should_allocate_from_space: NOT
returns NULL
{Heap before GC invocations=0 (full 0):
par new generation total 9216K, used 6980K [0x00000000fe200000, 0x00000000fec00000, 0x00000000fec00000)
eden space 8192K, 85% used [0x00000000fe200000, 0x00000000fe8d12b8, 0x00000000fea00000)
from space 1024K, 0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000feb00000)
to space 1024K, 0% used [0x00000000feb00000, 0x00000000feb00000, 0x00000000fec00000)
concurrent mark-sweep generation total 20480K, used 0K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 4515K, capacity 4638K, committed 4864K, reserved 1056768K
class space used 447K, capacity 462K, committed 512K, reserved 1048576K
4.491: [GC (Allocation Failure) Allocated 0 objects, 0 bytes concurrently4.492: [ParNewlevel=0 invoke=1 size=5242896CMS: promo attempt is safe: available(20971520) >= av_promo(0),max_promo(7148216) Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 1038272 bytes, 1038272 total
: 7148216->1048568(9437184), 0.0052765 secs] 7148216->6324312(30408704)Promoted 97 objects, 5265264 bytes Contiguous available 15530664 bytes , 0.0054440 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap after GC invocations=1 (full 0):
par new generation total 9216K, used 1023K [0x00000000fe200000, 0x00000000fec00000, 0x00000000fec00000)
eden space 8192K, 0% used [0x00000000fe200000, 0x00000000fe200000, 0x00000000fea00000)
from space 1024K, 99% used [0x00000000feb00000, 0x00000000febffff8, 0x00000000fec00000)
to space 1024K, 0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000feb00000)
concurrent mark-sweep generation total 20480K, used 5152K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 4515K, capacity 4638K, committed 4864K, reserved 1056768K
class space used 447K, capacity 462K, committed 512K, reserved 1048576K
} ###3 这里为什么会触发一次呢?上一次是49s触发,理论上来讲应该在52s左右触发,估计得看源码
CMS: promo attempt is safe: available(15695776) >= av_promo(5275744),max_promo(6291464) ###4 a2 分配完以后打印内存信息,此时eden区内存占用5219K,old区占用5152K
2020-05-29 14:59:50 a2 allocated,sleep 4s
2020-05-29 14:59:50 ======memory info start=======
Code Cache=init = 2555904(2496K) used = 2517568(2458K) committed = 2555904(2496K) max = 251658240(245760K)
Metaspace=init = 0(0K) used = 4624936(4516K) committed = 4980736(4864K) max = -1(-1K)
Compressed Class Space=init = 0(0K) used = 458088(447K) committed = 524288(512K) max = 1073741824(1048576K)
Par Eden Space=init = 8388608(8192K) used = 5345200(5219K) committed = 8388608(8192K) max = 8388608(8192K)
Par Survivor Space=init = 1048576(1024K) used = 1048568(1023K) committed = 1048576(1024K) max = 1048576(1024K)
CMS Old Gen=init = 20971520(20480K) used = 5275744(5152K) committed = 20971520(20480K) max = 20971520(20480K)
2020-05-29 14:59:50 ======memory info end======= ###5 cms gc 线程第二次执行,由上面Heap after GC日志可以看出此时old区内存占用率为5152/20480=25%,依然不满足触发条件
CMS: promo attempt is safe: available(15695776) >= av_promo(5275744),max_promo(6393768)
2020-05-29 14:59:54 a3 allocate start
TwoGenerationCollectorPolicy::mem_allocate_work: attempting locked slow path allocation
DefNewGeneration::allocate_from_space(1310722): will_fail: false heap_lock: locked free: 8 should_allocate_from_space: NOT
returns NULL
2020-05-29 14:59:54 a3 allocated,exit it
2020-05-29 14:59:54 ======memory info start=======
Code Cache=init = 2555904(2496K) used = 2527680(2468K) committed = 2555904(2496K) max = 251658240(245760K)
Metaspace=init = 0(0K) used = 4626464(4518K) committed = 4980736(4864K) max = -1(-1K)
Compressed Class Space=init = 0(0K) used = 458088(447K) committed = 524288(512K) max = 1073741824(1048576K)
Par Eden Space=init = 8388608(8192K) used = 5345200(5219K) committed = 8388608(8192K) max = 8388608(8192K)
Par Survivor Space=init = 1048576(1024K) used = 1048568(1023K) committed = 1048576(1024K) max = 1048576(1024K)
CMS Old Gen=init = 20971520(20480K) used = 15761520(15392K) committed = 20971520(20480K) max = 20971520(20480K)
2020-05-29 14:59:54 ======memory info end======= ###6 cms gc 线程第三次执行,由上面Heap after GC日志可以看出此时old区内存占用率为15761520/20971520=75%,依然不满足触发条件,但是却触发了cms gc
CMS: promo attempt is not safe: available(5210000) < av_promo(5275744),max_promo(6393768)
CMSCollector: collect because incremental collection will fail 10.498: [GC (CMS Initial Mark) 10.498: [
checkpointRootsInitialWork, 0.0015329 secs]
[1 CMS-initial-mark: 15761520(20971520)] 22155288(30408704), 0.0017536 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
10.500: [CMS-concurrent-mark-start]
10.500: [CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
10.500: [CMS-concurrent-preclean-start]
(modUnionTable: 0 cards)10.500: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
10.500: [CMS-concurrent-abortable-preclean-start]
CMS: promo attempt is not safe: available(5210000) < av_promo(5275744),max_promo(6393768)
10.500: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
10.500: [GC (CMS Final Remark) [YG occupancy: 6243 K (9216 K)]10.500: [checkpointRootsFinalWork10.500: [Rescan (parallel) , 0.0015643 secs]10.502: [refProcessingWork10.502: [weak refs processing, 0.0000342 secs]10.502: [class unloading, 0.0010723 secs]10.503: [scrub symbol table, 0.0006146 secs]10.504: [scrub string table, 0.0001729 secs], 0.0019787 secs], 0.0049316 secs][1 CMS-remark: 15761520(20971520)] 22155288(30408704), 0.0050250 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
10.505: [CMS-concurrent-sweep-start]
Collected 25 objects, 3144 bytes
Live 73 objects, 15747896 bytes Already free 123 objects, 5220480 bytes
Total sweep: 20971520 bytes
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 633548
Max Chunk Size: 630611
Number of Blocks: 3
Av. Block Size: 211182
Tree Height: 3
10.506: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] MetaspaceGC::compute_new_size:
minimum_free_percentage: 0.40 maximum_used_percentage: 0.60
used_after_gc : 4864.0KB
maximum_free_percentage: 0.70 minimum_used_percentage: 0.30
minimum_desired_capacity: 21296.0KB maximum_desired_capacity: 21296.0KB From compute_new_size:
Free fraction 0.248582
Desired free fraction 0.400000
Maximum free fraction 0.700000
Capactiy 20971
Desired capacity 26263
Younger gen size 9437
unsafe_max_alloc_nogc 5044
contiguous available 5044
Expand by 5292440 (bytes)
Expanded CMS gen for Free ratio
Expanded free fraction 0.248582
10.506: [CMS-concurrent-reset-start]
10.506: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ###7 周期性触发cms gc线程
15761520->15758376(20971520)CMS: promo attempt is not safe: available(5213144) < av_promo(5275744),max_promo(6393768)
CMSCollector: collect because incremental collection will fail 13.506: [GC (CMS Initial Mark) 13.506: [
checkpointRootsInitialWork, 0.0015626 secs]
[1 CMS-initial-mark: 15758376(20971520)] 22152144(30408704), 0.0017226 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
13.508: [CMS-concurrent-mark-start]
13.508: [CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
13.508: [CMS-concurrent-preclean-start]
(modUnionTable: 0 cards)13.509: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
13.509: [CMS-concurrent-abortable-preclean-start]
CMS: promo attempt is not safe: available(5213144) < av_promo(5275744),max_promo(6393768)
13.509: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
13.509: [GC (CMS Final Remark) [YG occupancy: 6243 K (9216 K)]13.509: [checkpointRootsFinalWork13.509: [Rescan (parallel) , 0.0015383 secs]13.510: [refProcessingWork13.510: [weak refs processing, 0.0000320 secs]13.511: [class unloading, 0.0014836 secs]13.512: [scrub symbol table, 0.0006510 secs]13.513: [scrub string table, 0.0001597 secs], 0.0024201 secs], 0.0048316 secs][1 CMS-remark: 15758376(20971520)] 22152144(30408704), 0.0048838 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
13.514: [CMS-concurrent-sweep-start]
Collected 0 objects, 0 bytes
Live 73 objects, 15747896 bytes Already free 79 objects, 5223624 bytes
Total sweep: 20971520 bytes
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 634511
Max Chunk Size: 630611
Number of Blocks: 6
Av. Block Size: 105751
Tree Height: 5
13.514: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] MetaspaceGC::compute_new_size:
minimum_free_percentage: 0.40 maximum_used_percentage: 0.60
used_after_gc : 4864.0KB
maximum_free_percentage: 0.70 minimum_used_percentage: 0.30
minimum_desired_capacity: 21296.0KB maximum_desired_capacity: 21296.0KB From compute_new_size:
Free fraction 0.248582
Desired free fraction 0.400000
Maximum free fraction 0.700000
Capactiy 20971
Desired capacity 26263
Younger gen size 9437
unsafe_max_alloc_nogc 5044
contiguous available 5044
Expand by 5292440 (bytes)
Expanded CMS gen for Free ratio
Expanded free fraction 0.248582
13.514: [CMS-concurrent-reset-start]
13.514: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 6243K [0x00000000fe200000, 0x00000000fec00000, 0x00000000fec00000)
eden space 8192K, 63% used [0x00000000fe200000, 0x00000000fe718fb0, 0x00000000fea00000)
from space 1024K, 99% used [0x00000000feb00000, 0x00000000febffff8, 0x00000000fec00000)
to space 1024K, 0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000feb00000)
concurrent mark-sweep generation total 20480K, used 15389K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 4524K, capacity 4638K, committed 4864K, reserved 1056768K
class space used 448K, capacity 462K, committed 512K, reserved 1048576K
ClassLoaderData CLD: 0x0000000015895070, loader: 0x00000000feb00000, loader_klass: 0x000000010000fa50 sun/misc/Launcher$ExtClassLoader { claimed handles 0x000000001576a350
metaspace: 0x00000000158da150

从gc日志里我写的注释可以看到###6 cms gc 线程第三次执行,由上面Heap after GC日志可以看出此时old区内存占用率为15761520/20971520=75%,依然不满足触发条件,但是却触发了cms gc不满足我之前的第9条预期,带着疑问接下来结合gc日志和openjdk源码尝试解答这个问题。

再看gc日志

CMS: promo attempt is not safe: available(5210000) < av_promo(5275744),max_promo(6393768)
CMSCollector: collect because incremental collection will fail 10.498: [GC (CMS Initial Mark) 10.498: [
checkpointRootsInitialWork, 0.0015329 secs]
[1 CMS-initial-mark: 15761520(20971520)] 22155288(30408704), 0.0017536 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
10.500: [CMS-concurrent-mark-start]
10.500: [CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
10.500: [CMS-concurrent-preclean-start]

其实日志的开头2行已经说明了原因,只是我知识面比较浅薄不知道cms gc触发不仅仅是“老年代占用率达到CMSInitiatingOccupancyFraction”这一个条件,从日志信息初判是因为老年代剩余空间小于平均晋升大小导致(CMS: promo attempt is not safe: available(5210000) < av_promo(5275744),max_promo(6393768) CMSCollector: collect because incremental collection will fail),这里扯个题外话,正常版本的jdk是打印不出来这种debug信息的,强烈建议下载debug版本的jdk学习jvm,当遇到阻碍的时候根据debug信息去网络上搜索会得到更有价值的信息。

结合日志看源码

"CMSCollector: collect because incremental collection will fail"是在concurrentMarkSweepGeneration.cpp中shouldConcurrentCollect方法输出的,简单读一下源码:

  1. 如果发生了full gc会触发,看下面日志是调用了System.gc或者gc_locker(这个没明白是什么场景)
//如果发生了full gc,看下面日志是调用了System.gc或者gc_locker(这个没明白是什么场景)
if (_full_gc_requested) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr("CMSCollector: collect because of explicit "
" gc request (or gc_locker)");
}
return true;
}

2.如果没有指定UseCMSInitiatingOccupancyOnly参数,jdk就按自己的规则来决定老年代当前的利用率是否会触发gc,这里分两种情况:

 2.1 根据历史数据做预测,如果cms执行完gc需要的时间大于老年代被填满的时间那就触发gc,具体的判断逻辑在time_until_cms_start中;

 2.2 没有历史数据的时候判断老年代利用率是否大于_bootstrap_occupancy,_bootstrap_occupancy是根据CMSBootstrapOccupancy计算出来的

// If the estimated time to complete a cms collection (cms_duration())
// is less than the estimated time remaining until the cms generation
// is full, start a collection. /**如果没有指定UseCMSInitiatingOccupancyOnly参数,jdk就按自己的规则来决定老年代当前的利用率是否会触发gc
分两种情况:
1.根据历史数据做预测,如果cms执行完gc需要的时间大于老年代被填满的时间那就触发gc,具体的判断逻辑在time_until_cms_start中
2.没有历史数据的时候判断老年代利用率是否大于_bootstrap_occupancy,_bootstrap_occupancy是根据CMSBootstrapOccupancy计算出来的
_bootstrap_occupancy = ((double)CMSBootstrapOccupancy)/(double)100,CMSBootstrapOccupancy是一个可传递参数,默认值为50
*/
if (!UseCMSInitiatingOccupancyOnly) {
//cms gc 只要被触发一次stats().valid()就会返回true
if (stats().valid()) {
if (stats().time_until_cms_start() == 0.0) {
return true;
}
} else {
//下面的这段逻辑只会被触发一次 // We want to conservatively collect somewhat early in order
// to try and "bootstrap" our CMS/promotion statistics;
// this branch will not fire after the first successful CMS
// collection because the stats should then be valid.
if (_cmsGen->occupancy() >= _bootstrap_occupancy) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr(
" CMSCollector: collect for bootstrapping statistics:"
" occupancy = %f, boot occupancy = %f", _cmsGen->occupancy(),
_bootstrap_occupancy);
}
return true;
}
}
}

3.执行ConcurrentMarkSweepGeneration::should_concurrent_collect判断逻辑,分以下几种情况:

 3.1 老年代利用率大于_initiating_occupancy会触发gc,_initiating_occupancy的值在CMSCollector被构造的时候通过CMSInitiatingOccupancyFraction计算出来;

 3.2 发生了扩容需要触发gc;

 3.3 _cmsSpace(CompactibleFreeListSpace)->should_concurrent_collect(这块没看明白)。

// Otherwise, we start a collection cycle if
// old gen want a collection cycle started. Each may use
// an appropriate criterion for making this decision.
// XXX We need to make sure that the gen expansion
// criterion dovetails well with this. XXX NEED TO FIX THIS
if (_cmsGen->should_concurrent_collect()) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr("CMS old gen initiated");
}
return true;
}
bool ConcurrentMarkSweepGeneration::should_concurrent_collect() const {

  assert_lock_strong(freelistLock());
//老年代利用率大于initiating_occupancy
if (occupancy() > initiating_occupancy()) {
if (PrintGCDetails && Verbose) {
gclog_or_tty->print(" %s: collect because of occupancy %f / %f ",
short_name(), occupancy(), initiating_occupancy());
}
return true;
}
if (UseCMSInitiatingOccupancyOnly) {
return false;
} //发生了扩容
if (expansion_cause() == CMSExpansionCause::_satisfy_allocation) {
if (PrintGCDetails && Verbose) {
gclog_or_tty->print(" %s: collect because expanded for allocation ",
short_name());
}
return true;
} //没看明白
if (_cmsSpace->should_concurrent_collect()) {
if (PrintGCDetails && Verbose) {
gclog_or_tty->print(" %s: collect because cmsSpace says so ",
short_name());
}
return true;
}
return false;
}

4.执行gch->incremental_collection_will_fail判断逻辑,本质上是预测young gc是否会失败,这里的预测标准有以下几种:

 4.1 最近一次已经是失败的;

 4.2 最近一次未失败,继续询问年轻代是否会失败(DefNewGeneration::collection_attempt_is_safe()),年轻代的判断逻辑如下:

  4.2.1 如果to区不为空,认为收集可能会失败(和to区是否为空有啥关系呢?)

  4.2.2 询问老年代晋升是否会失败(ConcurrentMarkSweepGeneration::promotion_attempt_is_safe),老年代判断剩余空间大小是否大于平均晋升空间大小或者大于当前年轻代使用的空间大小;

// We start a collection if we believe an incremental collection may fail;
// this is not likely to be productive in practice because it's probably too
// late anyway.
GenCollectedHeap* gch = GenCollectedHeap::heap();
assert(gch->collector_policy()->is_two_generation_policy(),
"You may want to check the correctness of the following"); //
if (gch->incremental_collection_will_fail(true /* consult_young */)) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print("CMSCollector: collect because incremental collection will fail ");
}
return true;
}
// Returns true if an incremental collection is likely to fail.
// We optionally consult the young gen, if asked to do so;
// otherwise we base our answer on whether the previous incremental
// collection attempt failed with no corrective action as of yet.
bool incremental_collection_will_fail(bool consult_young) {
// Assumes a 2-generation system; the first disjunct remembers if an
// incremental collection failed, even when we thought (second disjunct)
// that it would not.
assert(heap()->collector_policy()->is_two_generation_policy(),
"the following definition may not be suitable for an n(>2)-generation system");
return incremental_collection_failed() ||
(consult_young && !get_gen(0)->collection_attempt_is_safe());
}
bool DefNewGeneration::collection_attempt_is_safe() {
if (!to()->is_empty()) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print(" :: to is not empty :: ");
}
return false;
}
if (_next_gen == NULL) {
GenCollectedHeap* gch = GenCollectedHeap::heap();
_next_gen = gch->next_gen(this);
}
return _next_gen->promotion_attempt_is_safe(used());
}
bool ConcurrentMarkSweepGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
size_t available = max_available();
size_t av_promo = (size_t)gc_stats()->avg_promoted()->padded_average();
bool res = (available >= av_promo) || (available >= max_promotion_in_bytes);
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr(
"CMS: promo attempt is%s safe: available("SIZE_FORMAT") %s av_promo("SIZE_FORMAT"),"
"max_promo("SIZE_FORMAT")",
res? "":" not", available, res? ">=":"<",
av_promo, max_promotion_in_bytes);
}
return res;
}

5.判断MetaspaceGC _should_concurrent_collect标识

//5 读取should_concurrent_collect标识
if (MetaspaceGC::should_concurrent_collect()) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print("CMSCollector: collect for metadata allocation ");
}
return true;
} return false;
}

云里雾里

到这儿虽然有一些细节问题我还打着问号,但是也算是解决了心中的疑惑,触发cms gc的条件有好多个,大体归纳为以下几种:

1.发生了full gc,比如代码里显示的调用System.gc;

2.没有指定UseCMSInitiatingOccupancyOnly参数的时候jvm根据状态数据决定是否要触发gc,大原则就是在老年代填满之前触发gc将空间腾出来;

3.老年代利用率达到了CMSInitiatingOccupancyFraction或者老年代发生了扩容等;

4.ygc可能不安全,主要还是担心老年代剩余空间不足以容纳晋升对象(我这个小实验中就是因为这个条件触发了cms gc);

5.元空间_should_concurrent_collect标识为true;

总结

对任何参数的使用一定要经过亲自验证,否则直接应用到生产环境可能会带来不可预料的损失,如果发现和预期效果不一样那就去一步一步证实、论证,在互联网蓬勃发展的今天几乎任何问题都能找到蛛丝马迹,除非自己懒。这里再啰嗦一句,面对信息爆炸的时代如何甄别自己得到的信息是正确的呢?“纸上得来终觉浅,绝知此事要躬行”,多做实验多看源码才是学习的好办法。

参考资料

JVM发生CMS GC的 5 种情况,你知道的肯定不全!

garbage-collection-algorithms-implementations#concurrent-mark-and-sweep

来我的公众号与我交流

CMS垃圾收集器小实验之CMSInitiatingOccupancyFraction参数的更多相关文章

  1. G1垃圾收集器和CMS垃圾收集器 (http://mm.fancymore.com/reading/G1-CMS%E5%9E%83%E5%9C%BE%E7%AE%97%E6%B3%95.html#toc_8)

    参考来源 JVM 体系架构 堆/栈的内存分配 静态和非静态方法的内存分配 CMS 回收算法 应用场景 CMS 垃圾收集阶段划分(Collection Phases) CMS什么时候启动 CMS缺点 G ...

  2. CMS垃圾收集器

    介绍 CMS垃圾回收器的全称是Concurrent Mark-Sweep Collector,从名字上可以看出两点,一个是使用的是并发收集,第二个是使用的收集算法是Mark-Sweep.从而也可以推测 ...

  3. 实例透彻分析CMS垃圾收集器执行过程

    CMS收集器收集步骤: 在上一次[https://www.cnblogs.com/webor2006/p/11055468.html]中已经对CMS的垃圾收集器有了一定的理论上的了解,其中提到了CMS ...

  4. 稳了!我准备了1个晚上的CMS垃圾收集器

    面试官:今天还是来聊聊CMS垃圾收集器呗? 候选者:嗯啊... 候选者:如果用Seria和Parallel系列的垃圾收集器:在垃圾回收的时,用户线程都会完全停止,直至垃圾回收结束! 候选者:CMS的全 ...

  5. CMS垃圾收集器——重新标记和浮动垃圾的思考

    <深入理解java虚拟机 第二版 JVM高级特性与最佳实践>里面提到 CMS 垃圾收集器. CMS 垃圾收集器的垃圾回收分4个步骤: 初始标记(initial mark) 有 STW 并发 ...

  6. CMS垃圾收集器深入详解

    上一次[https://www.cnblogs.com/webor2006/p/11048407.html]对安全点和安全区进行了理论化的了解,接下来继续对CMS进行其它理论的了解,还是纯理论!!坚持 ...

  7. CMS垃圾收集器与G1收集器

    1.CMS收集器 CMS收集器是一种以获取最短回收停顿时间为目标的收集器.基于“标记-清除”算法实现,它的运作过程如下: 1)初始标记 2)并发标记 3)重新标记 4)并发清除 初始标记.从新标记这两 ...

  8. [转]使用CMS垃圾收集器产生的问题和解决方案

    在之前的一篇文章<CMS vs. Parallel GC>里通过实验的方式对比了并行和并发GC的优缺点,在文章结尾提到,CMS并行GC是大多数应用的最佳选择,然而, CMS并不是完美的,在 ...

  9. JVM的7种垃圾收集器:主要特点 应用场景 设置参数 基本运行原理

    原文地址:https://blog.csdn.net/tjiyu/article/details/53983650 下面先来了解HotSpot虚拟机中的7种垃圾收集器:Serial.ParNew.Pa ...

  10. 垃圾收集器必问系列—CMS

    本文已收录至Github,推荐阅读 Java随想录 微信公众号:Java随想录 CSDN: 码农BookSea 应该相信,自己是生活的战胜者.--雨果 目录 CMS简介 运作过程 CMS的缺陷 处理器 ...

随机推荐

  1. HMS Core机器学习服务实现同声传译,支持中英文互译和多种音色语音播报

    当用户有跨语种交流或音频内容翻译的需求时,应用需要能自动检测语音内容再输出为用户需要的语言文字. HMS Core机器学习服务提供同声传译能力,同声传译实现将实时输入的长语音实时翻译为不同语种的文本以 ...

  2. openGauss中的sequence跟Oracle的sequence有什么区别?

    openGauss 中的 sequence 跟 Oracle 的 sequence 有什么区别? openGauss 中也提供了 sequence 序列功能,使用 Oracle 的用户应该都非常喜欢使 ...

  3. Linux Ubuntu安装配置教程

    Ubuntu是一个基于Linux的开源操作系统,它遵循GNU通用公共许可证,用户可以自由使用.复制.分发和修改.它提供直观易用的桌面环境,适合新手和有经验用户.Ubuntu有强大的软件中心,支持多硬件 ...

  4. Docker之离线安装和在线安装

    一.离线安装 1.软件包下载 https://download.docker.com/linux/static/stable/x86_64/ 2.安装docker tar xvf /opt/docke ...

  5. redis 简单整理——缓存设计[三十二]

    前言 简单整理一下缓存设计. 正文 缓存的好处: ·加速读写:因为缓存通常都是全内存的(例如Redis.Memcache),而 存储层通常读写性能不够强悍(例如MySQL),通过缓存的使用可以有效 地 ...

  6. springboot+thymeleaf+mybatis实现甘特图(代码非常详细)

    首先我们要明白:这个甘特图需要哪些动态数据. (1)需要:ID,tName,number,计划开始时间,开始时间,计划结束时间,结束时间,项目负责人,参与人,知情人ID,计划时长(可以计算得出的,不必 ...

  7. 力扣453(java)-最小操作次数使数组元素相等(简单)

    题目: 给你一个长度为 n 的整数数组,每次操作将会使 n - 1 个元素增加 1 .返回让数组所有元素相等的最小操作次数. 示例 1: 输入:nums = [1,2,3]输出:3解释:只需要3次操作 ...

  8. 力扣901(java&python)-股票价额跨度(中等)

    题目: 编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度. 今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天) ...

  9. 力扣227(java)-基本计算器Ⅱ(中等)

    题目: 给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值. 整数除法仅保留整数部分. 你可以假设给定的表达式总是有效的.所有中间结果将在 [-231, 231 - 1] 的范围内. ...

  10. 链栈的实现 C语言/C++

    堆栈的链式存储C/C++实现--链栈 与顺序栈相比,链栈的优点在于不存在栈满上溢的问题.链栈通常使用单链表实现,进栈.出栈操作就是在单链表表头的 插入.删除操作.用单链表实现链栈时,使用不带头结点的单 ...