CMS(Concurrent Mark Sweep)收集器


算法:标记-清除
特点:关注最短停顿时间。
适用场景:与用户交互的服务端程序。
执行过程:
image.png

回收过程


初始标记

需要 Stop the world ,所有用户线程停止工作,但是时间很短。主要工作是标记老年代里面存活的 GC Root 对象,因为 GC Root 对象很少,所以速度很快。

并发标记

进行 GC Tracing。这个阶段,用户线程和垃圾回收线程同步工作,会有各种新的对象的加入和有对象失去引用成为垃圾。也会有新的 GC Root 对象的产生。

这个阶段中,垃圾回收器主要的工作是对老年代的对象进行追踪,看看它是否和 GC Root 有建立联系。如果没有则标记为垃圾。

这个阶段是最耗时的,但是由于是和用户线程同步进行的,所以不会对系统的运行造成影响(其实也不是,毕竟占用了 CPU 资源)。

重新标记

因为第二阶段是并发进行的,所以结束后,肯定会有存活对象和垃圾对象没有被标记出来。进入第三阶段就需要 Stop the world 。

主要进行,修正并发标记期间程序继续运行产生的一些变动。例如一些新的垃圾对象,新的 GC Root 等。这个阶段只是对第二阶段的一些变动的修正,或者补充,所以速度也是很快的。

并发清除

GC 线程和用户程序一起并发执行,清除垃圾对象。系统继续执行,而 GC 线程则将标记出来的垃圾对象进行清理。因为是并发进行,所以对系统的影响也不大。

缺点


占用 CPU 资源

由于在并发标记和并发清理阶段是需要追踪老年代的很多对象,清理的时候也需要在内存随机位置进行清理,所以耗时比较长。这两个耗时比较长的操作,都会占用 CPU 资源。

CMS 默认分配的 CPU 是 :(CPU核心数+3) / 4 。如果只有 2 核的服务器,那么垃圾回收线程就占了 (2+3)/4 = 1 个 CPU 线程,就占了一半的 CPU 资源了。

Concurrent Model Failure 问题

也就是并发模式失败。默认情况下,老年代内存占用率达到 92%,就会触发 CMS 进行垃圾回收(可以通过参数 -XX:CMSInitiatingOccupancyFraction 设置)。

在 CMS 执行的过程中,如果老年代的剩余空间不足以存放进入老年代的对象。那么就会使用 Serial Old 清理器代替 CMS ,进行 Stop the world ,进行长时间的 GC Root 标记,追踪,清理。进行完这一切后,才会恢复系统的运行。这种情况就成为 Concurrent Model Failure。

内存碎片问题

CMS 使用的是“标记-清除”算法,因为清除阶段是和用户线程并发进行,所以无法进行整理。所以导致清理过后会有很多的内存碎片。

因为碎片过多的原因,就算老年代有充足的空间,大对象无法找到连续的可用内存空间的时候,就会触发 Full GC。

为了避免频繁的 Full GC,默认情况下,每次进行 Full GC 后,就会进行一次内存碎片整理。由一下两个参数控制:
-XX:+UseCMS-CompactAtFullCollection 打开碎片整理开关。
-XX:CMSFullGCsBeforeCompaction 多少次 Full GC 后进行碎片整理,默认为0,就是说,每次 Full GC 后都会进行碎片整理。这个参数从 JDK 9 开始废弃。