垃圾收集器——CMS

2023-10-14  本文已影响0人  一直在路上_求名

概述

CMS 是 Concurrent Mark Sweep 的缩写,由名字可知是一款并行的用标记清除算法的收集器,其收集目标是以获取最短回收停顿时间的收集器,其是作用于老年代的垃圾收集器,要在Java虚拟机中启用 CMS 垃圾收集器,可以使用以下JVM参数:-XX:+UseConcMarkSweepGC。从Java 9 开始,CMS 被标记为不推荐使用,并在Java 14中被标记为已过时。Java 9及更高版本推荐使用G1垃圾收集器。因此,在选择垃圾收集器时,最好根据具体的应用场景和需求做出决策。

垃圾收集过程

CMS 在整个垃圾收集过程主要包含四个阶段,即初始标记、并发标记、重新标记、并发清除。
四个过程如图所示:


1491695650868_.pic.jpg

由上图可知,初始标记和重新标记阶段是需要停止用户线程的。

初始标记

初始标记即 Initial Mark 在这个阶段,CMS 会暂停应用程序的执行,标记出所有的根对象,并且标记出与根对象直接关联的对象。这个阶段的停顿时间较短。

并发标记

并发标记即 Concurrent Mark 在这个阶段,CMS 会与应用程序并发执行,标记出与根对象间接关联的对象,即进行 GCRootsTracing 的过程。这个过程需要的时间相对较长,但是与用户线程并发执行,因此影响较小。

重新标记

重新标记即 Remark 在这个阶段,CMS 会暂停应用程序的执行,重新标记出在并发标记阶段发生变化的对象。这个阶段的停顿时间较短。

并发清除

并发清除即 Concurrent Sweep 在这个阶段,CMS 会与应用程序并发执行,清除被标记为垃圾的对象。这个过程需要的时间相对较长,但是与用户线程并发执行,因此影响较小。

优缺点

优点

低停顿时间: CMS 的主要优势在于其低停顿时间。由于大部分工作是在并发执行的,所以应用程序的暂停时间相对较短。
适用于响应性应用: 适用于需要快速响应时间的应用程序,因为停顿时间较短。

缺点

内存碎片

由于 CMS 在清理垃圾时不会移动对象,可能导致内存碎片的增加。这可能在长时间运行的应用程序中导致性能问题。
空间碎片是由于标记清楚算法导致的,所以无法避免,但是 CMS 提供了一个 -XX:+UseCMSCompactAtFullCollection 用于在 CMS 收集器顶不住要进行 FullGC 时开启内存碎片整理过程,由于碎片的整理过程是无法并发的,所以停顿时间会变长,该参数默认开启。还提供了一个参数 -XX:CMSFullGCBeforeCompaction 表示执行多少次不压缩的 FullGC 后跟着一次带压缩的,默认为 0 表示每次 FullGC 都会压缩。

无法处理浮动垃圾

在 CMS 执行期间,应用程序可能会生成新的垃圾,而 CMS 无法处理这些浮动垃圾,可能需要等待下一次垃圾收集周期。
由于 CMS 收集器无法处理浮动垃圾,可能会出现 “Concurrent Mode Failure” 失败而导致另一次 FULL GC 的产生。因此需要预留一部分空间给并发收集过程中产生的浮动垃圾,可以使用 -XX:CMSInitiatingOccupancyFraction 来调节触发的百分比,默认该值为 92% 也就是当老年代使用了 92% 的空间时会激活 CMS 垃圾收集器。当出现 “Concurrent Mode Failure” 时 CMS 会使用 SerialOld 收集器收集。

注意点

CMS 是一个老年代收集器(不是整堆收集)。因此它必须把当前处于非收集区域的年轻代算作是 GCRoots。这跟一般的 young GC 时要把老年代的 remembered set部分算作 GCRoots 的道理一样,只不过HotSpot没有用卡表来记录young -> old引用,所以就干脆扫描整个年轻代作为GCRoots。
在 CMS 初始标记的上下文里,GCRoots 并不包括年轻代而是只有一些常规的 root。这是因为在接下来的CMS 并发标记阶段 CMS 会顺着初始的根集合把年轻代里的活对象都遍历了。所以从 CMS 初始标记 +并发标记 结合在一起的角度看,年轻代仍然是根集合的一部分(因为被扫描但不被收集)。
但既然 初始标记 +并发标记 已经扫过了年轻代为啥还要再在重新标记时再扫?这就是因为CMS使用的增量更新是一种 “grey mutator” 做法。CMS 重新标记阶段做的就是为了确保 grey mutator 正确性而重新扫描根集合,同时也要把卡表和 mod-union table 记录下的在老年代里发生了变化的引用也重新扫描一遍。
前面说了 CMS 增量更新的写屏障,只是在卡表记录一下改变的引用的出发端对应的卡页。那 mod-union table是啥? 其实很简单:卡表只有一份,既要用来支持 young GC 又要用来支持 CMS。每次 young GC 过程中都涉及重置和重新扫描卡表,这样是满足了 young GC 的需求,但却破坏了 CMS 的需求——CMS 需要的信息可能被 young GC 给重置掉了。 为了避免丢失信息,就在卡表之外另外加了一个 bitmap 叫做 mod-union table。在CMS 并发标记正在运行的过程中,每当发生一次young GC,当young GC要重置卡表里的某个记录时,就会更新 mod-union table 对应的bit。 这样,最后到CMS 重新标记的时候,当时的卡表外加 mod-union table 就足以记录在并发标记过程中老年代发生的所有引用变化了。
PS:最后一段引用 R大的解释。

上一篇下一篇

猜你喜欢

热点阅读