CMS 回收器(低延迟)

举报
程序员学长 发表于 2022/04/29 18:23:54 2022/04/29
【摘要】 CMS 回收器(低延迟)读前福利,最全pdf获取联系我在 JDK 1.5 时期,Hotspot 推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器:CMS(Concurrent Mark Sweep),这款收集器是 HotSpot 虚拟机中第一款真正意义上的并发收集器,它第一次实现了让 垃圾收集线程和用户线程同时工作 。CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间...

CMS 回收器(低延迟)

读前福利,最全pdf获取

联系我

在 JDK 1.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 或者 Serail 收集器中的一个。

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

image-20220420162449635.png

CMS 整个过程比之前的垃圾收集器更复杂,整个过程分为 4 个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段、和并发清除阶段(涉及 STW 的阶段主要是:初始标记阶段和重新标记阶段)。

  • 初始标记(Initial-Mark)阶段:在这个阶段中,程序中所有的工作线程将会因为“Stop The World”机制而出现短暂的暂停,这个阶段的主要任务仅仅是标记出 GCRoots 能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联的对象比较少,所以这里的速度非常快。
  • 并发标记阶段(Concurrent-Mark):从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要停止用户线程,可以和垃圾收集线程一起并发运行。
  • 重新标记(Remark)阶段:由于在并发标记阶段中,程序工作线程会和垃圾收集线程同时运行,因为为了修整并发标记期间,因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段长一些,但也远比并发标记阶段的时间短。
  • 并发清除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断为已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也可以和用户线程并发执行。

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

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

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

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

CMS 为什么不使用标记整理算法

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

优点

  • 并发收集
  • 低延迟

缺点

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

设置的参数

  1. -XX:+UseConcMarkSweepGC 手动指定使用 CMS 收集器执行垃圾收集任务,开启该参数后会自动将 -XX:+UseParNewGC 打开,即 ParNew (年轻代垃圾收集)+ CMS(老年代垃圾收集)+ Serail Old 的组合。
  2. -XX: CMSInitiatingOccupancyFraction 用于设置堆内存使用率阀值,一旦达到该阀值,便开始进行回收。JDK5以前版本的默认值是68,即当老年代空间使用率达到 68% 时,会执行一次 CMS 回收。JDK6 及以上版本默认值是 92。如果内存增长缓慢,则可以设置一个稍大一点的值,大的阀值可以有效降低 CMS 的触发频率,减少老年代回收次数,可以较为明显的改善应用程序性能。反之,如果应用程序内存使用率增长过快,则应该降低这个阀值,以避免频繁触发老年代串行回收器。我们可以通过该选项有效降低 FullGC的执行次数。
  3. -XX:UseCMSCompactAtFullCollection 用于指定在执行完 FullGC 后对内存空间进行压缩整理,以避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变的更长了。
  4. -XX:CMSFullGcBeforecompaction 设置在执行多少次 FullGC 后对内存空间进行压缩整理。
  5. -XX:ParallelCMSThread 设置 CMS 的线程数量。

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。