现象
使用jstat -gc
观察CMS FullGC的时候,发现每次到阈值回收的时候,FGC每次会跳2次:
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 mark
与final remark
,这两个阶段都是"stop the world",不过暂停时间较短
"GC次数"主要关心的其实是应用暂停次数。 要注意的是在CMS里"暂停次数"并不等同于"GC次数",CMS并发GC的一个周期叫"一次GC"但暂停了两次。
如果CMS并发GC过程中出现了concurrent mode failure的话那么接下来就会做一次mark-sweep-compact的full GC,这个是完全stop-the-world的。
图右边是CMS的两次标记
GMS的设计特点
1.低延迟
为了能达到低延迟的效果,CMS实际是把本来一次FullGC应该消息的时间,能过多次短的GC时间分滩了。就跟吃自助参一样,肚子就那么大,一次性拿一堆吃的可能要吃1个小时。如果一次拿一点点,一次吃10分钟,看上去次数多了,其实吃的量是一样的。
2.牺牲吞吐量
这样做的优点是勤拿少取,吃完10分钟可以干别的事,但是每次吃的少。缺点也之这出来了,吞吐量小。
验证
之前和同事聊到这个问题,做了个实验,补了两张图。
CMS在initial mark
和remark
会stop the world
,并切这两次是会记到FullGC
里
先看每一次 第二次,每一次都是两次FGC,但是上面的GC log中并未真正触发GC。 第三次