现象

使用jstat -gc观察CMS FullGC的时候,发现每次到阈值回收的时候,FGC每次会跳2次:

1
2
3
4
5
6
7
8
9
10
11
12
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
68096.0 68096.0 0.0 16853.2 545344.0 371165.3 8755648.0 5890791.6 62312.0 59746.5 7076.0 6608.5 43879 1312.752 60 5.206 1317.958
68096.0 68096.0 19377.9 0.0 545344.0 420827.3 8755648.0 5891528.9 62312.0 59746.5 7076.0 6608.5 43880 1312.785 60 5.206 1317.991
68096.0 68096.0 0.0 22147.2 545344.0 295435.0 8755648.0 5891577.4 62312.0 59746.5 7076.0 6608.5 43881 1312.816 60 5.206 1318.022
68096.0 68096.0 21257.5 0.0 545344.0 461020.2 8755648.0 5898299.9 62312.0 59746.5 7076.0 6608.5 43882 1312.850 60 5.206 1318.056
68096.0 68096.0 0.0 13895.9 545344.0 419534.8 8755648.0 5901746.3 62312.0 59746.5 7076.0 6608.5 43885 1312.961 60 5.206 1318.167
68096.0 68096.0 0.0 17542.7 545344.0 63902.2 8755648.0 5906856.1 62312.0 59746.5 7076.0 6608.5 43887 1313.028 60 5.206 1318.234
68096.0 68096.0 0.0 17542.7 545344.0 444878.0 8755648.0 5906856.1 62312.0 59746.5 7076.0 6608.5 43887 1313.028 60 5.206 1318.234
68096.0 68096.0 15241.8 0.0 545344.0 403475.0 8755648.0 5909706.0 62312.0 59746.5 7076.0 6608.5 43890 1313.108 60 5.206 1318.314
68096.0 68096.0 24031.2 0.0 545344.0 52019.0 8755648.0 5916571.4 62312.0 59746.5 7076.0 6608.5 43892 1313.174 60 5.206 1318.380
68096.0 68096.0 17381.4 0.0 545344.0 349457.1 8755648.0 5919082.7 62312.0 59746.5 7076.0 6608.5 43894 1313.232 60 5.206 1318.438
68096.0 68096.0 0.0 20887.7 545344.0 442091.1 8755648.0 5919151.3 62312.0 59746.5 7076.0 6608.5 43895 1313.261 60 5.206 1318.467

看这个日志,无论多过多久,每次FullGC的次数,必然是两次一起出现,也就是说 FullGC 是连续执行两次?!
为什么会这样,其实跟CMS这个回收器的特殊工作机制有关。

CMS的两次标记

JVM 的FullGC通常需要先stop-the-world才进行回收。一次stop-the-world的时长就是整个GC回收的时长。
CMS的工作流程中,有两个阶段是会触发STW的:initial markfinal remark,这两个阶段都是"stop the world",不过暂停时间较短

"GC次数"主要关心的其实是应用暂停次数。
要注意的是在CMS里"暂停次数"并不等同于"GC次数",CMS并发GC的一个周期叫"一次GC"但暂停了两次。

如果CMS并发GC过程中出现了concurrent mode failure的话那么接下来就会做一次mark-sweep-compact的full GC,这个是完全stop-the-world的。

图右边是CMS的两次标记

cms回收流程

GMS的设计特点

1.低延迟

为了能达到低延迟的效果,CMS实际是把本来一次FullGC应该消息的时间,能过多次短的GC时间分滩了。就跟吃自助参一样,肚子就那么大,一次性拿一堆吃的可能要吃1个小时。如果一次拿一点点,一次吃10分钟,看上去次数多了,其实吃的量是一样的。

2.牺牲吞吐量

这样做的优点是勤拿少取,吃完10分钟可以干别的事,但是每次吃的少。缺点也之这出来了,吞吐量小。

验证

之前和同事聊到这个问题,做了个实验,补了两张图。
CMS在initial markremarkstop the world,并切这两次是会记到FullGC

先看每一次
跳两次1
第二次,每一次都是两次FGC,但是上面的GC log中并未真正触发GC。
跳两次2
第三次
跳两次3