jvm学习 CMS垃圾收集器通俗理解

系统性学习请点击jvm学习目录
前面讲了垃圾回收算法,那么就开始介绍垃圾回收器吧,总共要写CMS,G1,Shenandoah和ZGC。

总览CMS

CMS垃圾收集器,其全称是Concurrent Mark Sweep,并发标记清除。
从其名字Concurrent可以看出,首先它是并发的,这表明它与Serial收集器以及Parnew收集器或者Parallel Scavenge收集器这些单线程或者并行的收集器是不一样的,这里是并发的,并发代表,在进行垃圾收集的过程中,用户线程也是可以并发进行的。
而从其名字Mark Sweep可以知道,它是使用标记清除算法的(如果不清楚可,这里可以看执之前的垃圾回收算法)。使用了标记-清除算法,很明显,它会很快,但也同时很明显,它会产生内存碎片(内存不连续)。
总结一下,其最主要的特点是:

  • CMS垃圾收集器是面向老年代的,所以在实际jvm使用中,常常会会搭配其他新生代的垃圾收集器来配套使用。
  • CMS垃圾收集器的目的是追求时延最短,也就是停顿时间最短,它倾向于单次STW时间少,所以CMS垃圾收集器比较适合对响应速度要求较高的服务。
  • 并发收集,低停顿。

CMS收集器在垃圾回收过程中经历四个阶段:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

在这里插入图片描述

下面对这四个阶段分别进行介绍。

初始标记

该阶段将GC Roots对象所直接引用的对象进行标记(这里如果对GC Roots不熟悉可以点击可达性分析)。所谓直接引用的对象也就是GC Roots对象节点的子节点。如下图所示(画的丑陋,见谅哈哈哈哈,然后这里是三色标记法,黑色是子节点与本节点被扫描过,灰色是只有本节点被扫描过,而白色是本节点未被扫描过):
在这里插入图片描述

黑色的就是初始标记中被标记的,而白色的就是没有被标记的。
实际情况跟我画的图不一样哈,实际情况中,这颗树会很大的,GC Roots其直接引用的对象其实特别少,所以初始标记特别快,快到反应不过来。
但这里需要注意的是,初始标记是不能被其他用户线程干扰的,即使它很快,它也会STW(stop the world,该阶段只允许GC线程运行,不允许用户线程运行)。

并发标记

在并发标记阶段,CMS会将剩下没有扫描的对象全部都扫描,也就是将可达树上完完整整的遍历一遍,这个过程耗时较长,但是它不需要停顿。该阶段是GC线程与用户线程并发执行的。执行完之后的结果如下图所示:
在这里插入图片描述
因为是并发执行的,所以该阶段当然就不会STW啦。
在该阶段,如果用户线程改变了对象间的引用关系,JVM会将新插入的引用记录下来,等到重新标记这个阶段来重新扫描被记录的节点。

重新标记

既然是并发标记,那么自然就是个并发可达性分析问题,当标记过程中用户线程改变了对象引用的关系时,那么就会出问题。
下面简单讲一下会出什么问题,详细了解请点击JVM学习 并发可达性分析详解,如果这一块比较了解的话,下面的例子就不用看了,直接跳到黑字部分
问题在于:
在确定了GC Roots之后,我们继续往下扫描,此时用户线程可能会对可达性分析造成干扰,见下图。
在这里插入图片描述
这里JVM从0这个GC Roots开始扫描,当扫描了2时,正准备扫描其子节点3时,此时用户线程对引用关系进行了修改,将2-3的引用关系删除,添加了1-3的引用关系,如下图所示(虚线是删除)。

在这里插入图片描述
可以看到此时出问题了,即使是修改了之后,从我们用户的眼光来看,3依然是可达的,但是在jvm角度来看,1是黑的,它和它的子节点不会再被扫描,所以扫描不到3,2是灰的,但是它与3之间没有引用关系。此时,jvm就会错误的认为3是不可达的。

于是,为了摆脱这种错误出现的可能性,CMS采用了增量更新的办法,即:当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等我们的扫描(遍历)结束之后,在以我们记录中的黑色对象为GC Roots,再扫描一遍。这里需要注意,再次扫描的不是整个树哦,而是那些有过添加引用操作的黑色对象。所以第二次扫描的时间会很短。

这里在并发标记阶段,CMS就正常的扫描,同时将新插入的引用关系记录下来,而在重新标记阶段,则会重新扫描被记录被新插入引用关系的节点,从而将该节点下的新插入的子节点都扫描出来。从而保证了“不错杀一个好人”。

经过上面的叙述,应该对重新标记这个阶段有了深刻的理解了吧,总结一下,重新标记阶段就是为了修正并发标记期间,用户线程继续执行而导致标记产生变动的的那一部分对象的标记记录。由于这个阶段只是修正,所以执行时间比并发标记要短的多,不过也比初始标记要长一些。
同时,该阶段是要STW的,也就是非并发的。你可以这样想,重新标记就是为了解决上一阶段并发所搞得烂摊子,如果这里还并发,那么和上个阶段有什么区别?所以为了修正,为了不再弄出问题,该阶段是一定要STW的

并发清除

该阶段是最后一个阶段,只是将经过前面三个标记阶段最终被判断为死亡的对象给清除了就行。只是这个阶段是并发的,也没影响,因为判断被死亡的对象肯定是死的透透的,不可能复活的。
不过如果是这个阶段,堆内存中出现了新的垃圾,比如说已经被标记为黑色的对象变成了垃圾,CMS也无能为力了,只能等到下一次major gc才能解决。这些新出现的垃圾被取名为“浮动垃圾”。这也是CMS的一个局限性。

总结

整体来看,整个CMS过程还是比较简单的,我们记忆它可以采取这样的记忆法:CMS=并发可达性分析∪标记清除算法,然后记住初始标记和重新标记是STW的基本就可以了。
虽说4个阶段里有两个阶段是要STW的,但实际两个阶段的时间是非常短的,所以也是符合了追求低时延的目标了。
然后需要切记的是,该收集器是面向老年代的,千万不要弄混了,不要扯到新生代那里去了。
下面来说下CMS的优点:

  • 并发
  • 低时延

而缺点也显然:

  • 浮动垃圾无法处理,假如浮动垃圾过多,可能直接导致full gc,这里需要提醒,不要觉得full gc就是简单的minor gc+major
    gc,full gc会启用Serial
    old收集器来处理老年代,它是单线程的非并发的,会STW很久,与CMS的低时延相比,这个简直停顿到爆炸。
  • 因为采用的标记-清除算法,所以毫无疑问会导致内存不连续,产生内存碎片。
  • CMS的回收线程数是(处理器核心数量+3)/4,当处理器数量少时,占用的cpu资源会比较多,此时就不是很友好了,所以CMS比较适合多核心CPU的PC。

参考资料

  • 《深入理解jvm》周志明
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值