CMS收集器实测,发现不一样的知识点!

都知道Java的CMS收集器分4个步骤,但运行测试却发现并不是!

CMS收集器标准步骤与测试

CMS作为JVM中的老年代收集器,通过与用户线程的并发实现了较短的停顿时间,总体步骤分为初始标记、并发标记、重新标记、并发清理。如下图:
1CMS基本步骤
运行代码验证
虽然总结出了CMS收集器的步骤,但是不实践那都是假的,所以用代码运行了下,首先配置jvm参数:-verbose:gc、-Xms20M、-Xmx20M、-Xmn10M、-XX:+PrintGCDetails、-XX:SurvivorRatio=8、-XX:+UseConcMarkSweepGC,最后一个参数指定使用CMS收集器,每个收集器使用范围是确定,比如CMS是老年代收集器,所以不用指定使用的它使用的区域(G1收集器新生代老年代都用)。
代码如下图:
2简单代码.png
运行结果如下图:
3运行结果.png
代码解释:新生代只有9M(有1M在to survivor不算在内),jvm在运行差不多要用2、3M左右,当分配arry2的时候新生代内存就不够了,所以进行了一次新生代回收,使用的是ParNew(我的收集器默认是Parallel Scavenage和Parallel Old组合,但是设置了老年代为CMS,新生代收集器就自动变成了ParNew,因为CMS不与Parallel Scavenage搭配使用)。

同时由于数组是4M,survivor只有1M不能存放arry1,所以arry1在第一次回收的时候就直接进入老年代了。在准备放入arry3的时候再次进行了ParNew收集,因为新生代回收ParNew: 5055K->250K,但是总内存回收9153K->9152K,整体基本没有回收,说明这一次只是把新生代的晋升到老年代而已。

现在看CMS收集:CMS-initial-mark: 8825K(10240K),使用率8825/10240约等于86%,而默认使用率达到92%才会触发收集,不应该触发CMS收集(CMS收集器如果不指定使用率CMSInitiatingOccupancyFraction,默认是通过其他参数计算得来CMSInitiatingOccupancyFraction=(100-MinHeapFreeRatio)+(CMSTriggerRatio*MinHeapFreeRatio/100),可以通过配置-XX:+PrintFlagsFinal参数得要CMSTriggerRatio、MinHeapFreeRatio)。

这里触发了收集是因为老年代剩余内存小于每次YGC时晋升到老年代对象大小的平均值。如果这个程序运行的时间够长,你就会发现后面一直在执行CMS收集,比如像下面代码一样。
如下图:
4多次CMS回收.png
只要程序一直运行下去,CMS就会一直在回收。

CMS收集器的步骤详解

从第一个运行结果图来看,CMS并不是我们想的那么简单,实际步骤有:CMS-initial-mark、CMS-concurrent-mark、CMS-concurrent-preclean、CMS-concurrent-abortable-preclean、CMS Final Remark、CMS-concurrent-sweep、CMS-concurrent-reset,一共7个步骤

1、CMS-initial-mark:标记那些直接被GC root引用或者被年轻代存活对象所引用的所有对象
2、CMS-concurrent-mark:并发标记阶段,它会根据上个阶段找到的GC Roots遍历查找,它是与用户的应用程序并发运行的。
3、CMS-concurrent-preclean:并发预清理,解决的是跨代引用问题。
4、CMS-concurrent-abortable-preclean:abortable并发清理,这里解决的是并发标记问题。
5、CMS Final Remark:最终标记,由于前几步都是并发执行的,可能老年代有新的对象进来(比如大对象),这个阶段的目标是标记老年代所有的存活对象,所以这一步是STW(Stop The World)。
6、CMS-concurrent-sweep,这里不需要STW,它是与用户的应用程序并发运行,这个阶段是清除那些不再使用的对象,回收它们的占用空间为将来使用。
7、Concurrent Reset,这个阶段也是并发执行的,它会重设CMS内部的数据结构,为下次的GC做准备。

分析下来实际上就多了三个步骤,最后一步Concurrent Reset可以想到,其中3、4步对应我之前的一篇文章:《GC两个关键难点:跨代引用与并发标记》中讲的为了解决跨代引用和并发标记的问题。

通过每个过程的名称可以看出处理1、5是要STW的,其他的都是和用户线程并发的,可以改造代码验证,如下图:
5并发验证.png

就可以看到收集器和用户线程的并发过程,如果不能体现可以多运行几次或者增加遍历次数。

总结

通过对CMS收集器的实测,知道了并不一定老年代使用率达到了指定的使用率才会触发CMS收集,它还会根据每次YGC(新生代收集)平均进入老年代对象大小是否大于老年代剩余内存来进行回收,如果进入老年代平均内存大于了老年代剩余内存就会触发CMS收集,如果收集完成后仍然是进入老年代平均内存大于了老年代剩余内存,那么可能会一直触发CMS收集。

看来把CMS收集器步骤简单归纳为初始标记、并发标记、重新标记、并发清理并不准确,作为唯一一个只收集老年代的并发收集器,它分别用了两个步骤去处理了跨代引用与并发标记的问题(在之前的文章《GC两个关键难点:跨代引用与并发标记》有具体解释)。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!
Java程序员日常学习笔记

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值