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方法輸出的,簡單讀一下源碼:
- 如果發生了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
