0%

JVM - 05 java垃圾收集器:CMS收集器

CMS收集器(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。如:互联网站或B/S系统的服务端,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器非常符合这类应用的需求。

使用-XX:+UseConcMarkSweepGC打开。
CMS执行的扫描、着色和清除步骤如下。
(1) 初始标记:只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
(2) 并发标记:进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
(3) 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
(4) 并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。

Alt text

CMS的缺点:

(1) CMS对CPU资源非常敏感。其默认启动的收集线程数=(CPU数量+3)/4,在CPU少于4个,用户程序本来CPU负荷已经比较高的情况下,如果还要分出CPU资源用来运行垃圾收集器线程,会使得CPU负载加重。
(2) 无法处理浮动垃圾,可能出现” Concurrent Mode Failure “而导致另一次Full GC的产生:由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了“Floating Garbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉。CMS收集器默认在旧生代在使用68%的空间后就会激活。可以通过参数-XX:CMSInitiatingOccupancyFraction来设置百分比。
如果在CMS运行期间,预留的内存无法满足程序需要,就会出现一次Concurrent Mode Failure失败,此时虚拟机将启动预备方案,使用Serial Old收集器重新进行旧生代垃圾回收。
(3) CMS收集器使用”标记-整理算法”实现,所以其很容易产生内存碎片,而降低了内存空间的利用率。为了解决这个问题,CMS提供了一个整理碎片的功能,可通过-XX:+UseCMSCompactAtFullCollection来启动此功能。在启动此功能后默认为每次执行Full GC时都会进行整理,也可通过-XX:CMSFullGCsBeforeCompaction=来指定多少次Full GC后才执行整理。值得注意的是,整理这个步骤是需要暂停整个应用的。

CMS算法被废弃原因

CMS(JVM中的垃圾收集器)被废弃主要是因为其内存碎片化严重、并发模式失败(Concurrent Mode Failure)的风险以及对CPU资源消耗大。这些问题导致它在某些场景下停顿时间可能比其他收集器更长,例如当预留内存不足时会触发长时间的Stop-The-World Full GC,而这些缺陷在后来的G1等收集器中得到了更好的解决。

主要原因

  • 内存碎片化:CMS采用“标记-清除”算法,直接删除垃圾对象而不进行整理,长期运行后会导致堆内存出现大量碎片。这可能导致明明有剩余空间,但无法分配给大对象,从而触发另一次Full GC来整理碎片。
  • 并发模式失败 (Concurrent Mode Failure):CMS为了降低停顿时间,在并发标记和并发清除期间会与应用程序线程并行工作。如果并发期间应用程序产生的垃圾过多,超出了预留空间,CMS就会发生“并发模式失败”,并回退到使用Serial Old收集器进行一次长时间的Stop-The-World Full GC来处理,此时的停顿时间反而更长。
  • 高CPU消耗:CMS在并发回收阶段需要额外的CPU资源来运行垃圾收集线程,这在高CPU资源竞争的场景下会显著影响系统吞吐量,导致性能下降。
  • 浮动垃圾 (Floating Garbage):在并发标记和并发清除阶段,会有一些新的垃圾对象在回收过程中产生。这些对象无法在当前周期中被回收,只能留到下一次GC,它们会占用一部分堆空间。
  • 复杂的实现和维护:相较于其他收集器,CMS的设计更加复杂,也容易出现一些难以预料的bug和维护难度。
  • 替代方案更优:随着JVM的发展,G1(Garbage-First)等新的垃圾收集器在并发和碎片处理上提供了更好的解决方案,并逐渐成为默认收集器。

CMS相关参数

参数 描述
-XX:+UseConcMarkSweepGC 使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现”Concurrent Mode Failure”失败后的后备收集器使用。
-XX:ParallelCMSThreads 设置CMS的线程数
-XX:CMSInitiatingOccupancyFraction 设置CMS收集器在旧生代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSCompactAtFullCollection 由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效
-XX:+CMSFullGCBeforeCompaction 设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用
-XX:+CMSClassUnloadingEnabled 允许对类元数据进行回收
-XX:+CMSParallelRemarkEnabled 降低标记停顿
-XX:CMSInitiatingPermOccupancyOnly 当持久代占用率达到这一百分比时,才进行CMS回收(前提-XX:+CMSClassUnloadingEnabled激活了)
-XX:+UseIncrementaMode 使用增量模式,比较适合单CPU

总结

尽管CMS在引入之初是为了降低GC停顿时间,但其固有的缺陷,特别是内存碎片化和并发模式失败,使其在实际应用中表现不稳定,且对CPU的压力较大。最终,Java官方决定在JDK 14中移除CMS,推荐用户使用性能更优越的替代方案。

– end –