CMS垃圾回收器

CMS 回收器:低延迟

在 JDK1.5 时期,Hotspot 推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器:CMS(Concurrent-Mark-Sweep)收集器,这款收集器是 HotSpot 虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作

CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验。

  • 目前很大一部分的 Java 应用集中在互联网站或者 B/S 系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS 收集器就非常符合这类应用的需求。

CMS 的垃圾收集算法采用标记-清除算法,并且也会"Stop-the-World"

不幸的是,CMS 作为老年代的收集器,却无法与 JDK1.4.0 中已经存在的新生代收集器 Parallel Scavenge 配合工作,所以在 JDK1.5 中使用 CMS 来收集老年代的时候,新生代只能选择 ParNew 或者 Serial 收集器中的一个。

在 G1 出现之前,CMS 使用还是非常广泛的。一直到今天,仍然有很多系统使用 CMS GC。

image-20200713205154007

CMS 整个过程比之前的收集器要复杂,整个过程分为 4 个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段

  • 初始标记(Initial-Mark)阶段:在这个阶段中,程序中所有的工作线程都将会因为“Stop-the-World”机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出 GCRoots 能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快
  • 并发标记(Concurrent-Mark)阶段:从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。(三色标记法132[深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 周志明.pdf](file:///C:/Users/wu/Desktop/深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 周志明.pdf))
  • 重新标记(Remark)阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。
  • 并发清除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的

尽管 CMS 收集器采用的是并发回收(非独占式),但是在其初始化标记和再次标记这两个阶段中仍然需要执行“Stop-the-World”机制暂停程序中的工作线程,不过暂停时间并不会太长,因此可以说明目前所有的垃圾收集器都做不到完全不需要“stop-the-World”,只是尽可能地缩短暂停时间。

由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。

另外,由于在垃圾收集阶段用户线程没有中断,所以在 CMS 回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS 收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在 CMS 工作过程中依然有足够的空间支持应用程序运行。要是 CMS 运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure” 失败,这时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。

CMS 收集器的垃圾收集算法采用的是标记清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地将会产生一些内存碎片。那么 CMS 在为新对象分配内存空间时,将无法使用指针碰撞(Bump the Pointer)技术,而只能够选择空闲列表(Free List)执行内存分配。

image-20200713212230352

有人会觉得既然 Mark Sweep 会造成内存碎片,那么为什么不把算法换成 Mark Compact?

答案其实很简单,因为当并发清除的时候,用 Compact 整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact 更适合“Stop the World” 这种场景下使用

1. CMS 的优点

  • 并发收集
  • 低延迟

2. CMS 的弊端

  • 会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发 FullGC。
  • CMS 收集器对 CPU 资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
  • CMS 收集器无法处理浮动垃圾。可能出现“Concurrent Mode Failure"失败而导致另一次 Full GC 的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS 将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行 GC 时释放这些之前未被回收的内存空间。

3. 设置的参数

  • -XX:+UseConcMarkSweepGC 手动指定使用 CMS 收集器执行内存回收任务。

    开启该参数后会自动将-xx:+UseParNewGC打开。即:ParNew(Young 区用)+CMS(Old 区用)+ Serial Old 的组合。

  • -XX:CMSInitiatingOccupanyFraction 设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。

    • JDK5 及以前版本的默认值为 68,即当老年代的空间使用率达到 68%时,会执行一次 CMS 回收。JDK6 及以上版本默认值为 92%
    • 如果内存增长缓慢,则可以设置一个稍大的值,大的阀值可以有效降低 CMS 的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低 Ful1Gc 的执行次数。
  • -XX:+UseCMSCompactAtFullCollection 用于指定在执行完 Full GC 后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。

  • -XX:CMSFullGCsBeforeCompaction 设置在执行多少次 Full GC 后对内存空间进行压缩整理。

  • -XX:ParallelcMSThreads 设置 CMS 的线程数量。

    • CMS 默认启动的线程数是(ParallelGCThreads+3)/4,ParallelGCThreads 是年轻代并行收集器的线程数。当 CPU 资源比较紧张时,受到 CMS 收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。

小结

HotSpot 有这么多的垃圾回收器,那么如果有人问,Serial GC、Parallel GC、Concurrent Mark Sweep GC 这三个 Gc 有什么不同呢?

请记住以下口令:

  • 如果你想要最小化地使用内存和并行开销,请选 Serial GC;
  • 如果你想要最大化应用程序的吞吐量,请选 Parallel GC;
  • 如果你想要最小化 GC 的中断或停顿时间,请选 CMS GC。

4. JDK 后续版本中 CMS 的变化

JDK9 新特性:CMS 被标记为 Deprecate 了(JEP291)

  • 如果对 JDK9 及以上版本的 HotSpot 虚拟机使用参数-XX: +UseConcMarkSweepGC来开启 CMS 收集器的话,用户会收到一个警告信息,提示 CMS 未来将会被废弃。

JDK14 新特性:删除 CMS 垃圾回收器(JEP363)

  • 移除了 CMS 垃圾收集器,如果在 JDK14 中使用 -XX:+UseConcMarkSweepGC的话,JVM 不会报错,只是给出一个 warning 信息,但是不会 exit。JVM 会自动回退以默认 GC 方式启动 JVM
<think>嗯,用户问的是JavaCMS垃圾回收器,我需要先回忆一下CMS的基本信息。CMS全称是Concurrent Mark Sweep,属于老年代的垃圾回收器,对吧?它的目标主要是减少停顿时间,适合对延迟敏感的应用。那CMS的工作流程是怎样的呢?我记得分为几个阶段,比如初始标记、并发标记、重新标记,还有并发清除这些步骤。初始标记应该很快,只是标记GC Roots直接关联的对象,然后并发标记是和用户线程一起运行的,这样减少停顿。然后是重新标记阶段,用来处理并发期间变化的对象,最后并发清除回收垃圾。 用户可能想知道CMS的优点和缺点。优点应该是低停顿,适合需要快速响应的应用。但缺点呢?比如内存碎片问题,因为CMS是基于标记-清除算法的,会产生碎片,可能导致大对象分配失败触发Full GC。还有就是并发阶段消耗CPU资源,可能会影响吞吐量。另外,CMS需要预留空间给并发收集期间新产生的对象,如果预留不够就会导致并发模式失败,转而使用Serial Old收集器,这样停顿时间反而更长。 可能用户在使用CMS时遇到了问题,或者在选择GC策略时需要权衡。比如,CMSJava 8之后已经被标记为废弃,G1成为了默认的老年代收集器,但有些旧系统可能还在用CMS。用户可能想了解如何配置CMS的参数,比如-XX:+UseConcMarkSweepGC来启用,或者设置初始标记的阈值,调整并发线程数等。 另外,CMS的三色标记法可能涉及到漏标的问题,通过写屏障来记录并发期间的引用变化,确保最终标记的准确性。这部分可能需要详细解释,但用户可能更关注实际应用,所以应该简明扼要。 还需要提到CMS的几个关键参数,比如CMSInitiatingOccupancyFraction,控制老年代占用多少时开始GC,默认是68%。还有-XX:+CMSScavengeBeforeRemark,是否在重新标记前进行一次Young GC,减少重新标记的时间。 用户可能没有提到CMS的替代方案,比如G1,是否需要简单对比一下?不过问题集中在CMS本身,可能不需要展开。但可以作为相关问题提出来。 总结回答的结构:先介绍CMS是什么,适用场景,工作流程的各个阶段,优缺点,关键参数,以及废弃状态。然后相关问题应该包括工作流程细节、替代方案、参数调优、内存碎片处理,以及废弃原因等。</think>CMS(Concurrent Mark Sweep)垃圾回收器Java早期版本中用于老年代的低停顿垃圾回收器,其核心目标是通过并发执行减少垃圾回收过程中的停顿时间。以下是关键要点: ### 核心流程 1. **初始标记(Initial Mark)** 仅标记GC Roots直接关联的对象(速度快,需Stop-The-World)。 2. **并发标记(Concurrent Mark)** 与用户线程并发执行,遍历对象引用链(无需停顿)。 3. **重新标记(Remark)** 修正并发标记期间因用户线程运行导致的引用变化(需短暂停顿)。 4. **并发清除(Concurrent Sweep)** 并发回收垃圾对象(无需停顿)。 ### 优缺点 - **优点**:低停顿,适合高响应场景(如Web服务)。 - **缺点**: - 内存碎片化(标记-清除算法导致)。 - 并发阶段占用CPU资源。 - **并发模式失败**(Concurrent Mode Failure)风险:若老年代空间不足,会触发Full GC并退化为Serial Old收集器。 ### 关键参数 - `-XX:+UseConcMarkSweepGC`:启用CMS(JDK9前)。 - `-XX:CMSInitiatingOccupancyFraction=68`:设置老年代占用阈值触发GC(默认68%)。 - `-XX:+CMSScavengeBeforeRemark`:在重新标记前强制执行Young GC(减少重标记时间)。 ### 注意 CMS在JDK9中被废弃,JDK14后移除,推荐使用G1或ZGC等新一代回收器。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值