CMS垃圾收集器详解

CMS垃圾收集器详解概述 CMS 垃圾收集器是一款优秀的老年代并发垃圾收集器 通过与用户线程并发执行的方式减少 GC 停顿的时间 本文主要聊一下 CMS 设计到的相关的数据结构 具体的执行过程 运行中会出现的异常情况 在 CMS 之前并行垃圾收集器通过下图方式进行 虽然 GC 阶段多线程并行执行单此时用户线程是完全暂停的 如果 GC 时间过长 将引发服务响应超时 调用接口超时等各类异常 而 CMS 垃圾收集器大部分时间 GC 线程与用户线程并发执行 只有在初始标记和重新标记阶段才暂停用户线程总体思路 当达到 GC 条件时 开始并发标记存

概述

CMS垃圾收集器详解

在CMS之前并行垃圾收集器通过下图方式进行,虽然GC阶段多线程并行执行单此时用户线程是完全暂停的。如果GC时间过长,将引发服务响应超时、调用接口超时等各类异常。

而CMS垃圾收集器大部分时间GC线程与用户线程并发执行,只有在初始标记和重新标记阶段才暂停用户线程

CMS垃圾收集器详解

初始标记

暂停应用程序线程,遍历GC ROOTS直接可达的对象并将其压入标记栈(mark-stack)。标记完之后恢复应用程序线程。

并发标记 

这个阶段虚拟机会分出若干线程(GC 线程)去进行并发标记。标记哪些对象呢,标记那些GC ROOTS最终可达的对象。具体做法是推出标记栈里面的对象,然后递归标记其直接引用的子对象(如果遇到地址比当前对象低的对象则标记并压如栈中,遇到地址比当前对象高则只标记不入栈,同样的把子对象压到标记栈中,重复推出,压入。。。直至清空标记栈。这个阶段GC线程和应用程序线程同时运行。

CMS垃圾收集器详解

三色标记详解:三色标记法与读写屏障 – 简书

这种条件下可能会出现活动对象的漏标的情况,比如下面场景:

CMS垃圾收集器详解

活动对象被遗漏标记

A是活动对象,A->B,标记B可达,将其压入标记栈,此时A所有直接子对象遍历完,A出栈,标记线程将不会再访问A。

同时应用程序移掉了B对C的引用,让A重新引用C。

B出栈时无法标记C可达,A虽然引用C但标记线程不会再访问A,此时C会被当成不可达对象

并发过程中变化的维护card table与mod union table

card table

CMS中一个与YGC相关并十分重要的数据结构是:卡表(card table)。之所以出现卡表这样的一个数据结构是因为:YGC时为了标记活动标记对象除了tracing GC ROOTS之外, 别忘了老年代里也可能会引用新生代对象。所以正常来说还要扫描一次老年代,如果是扫描整个老年代这将会随着堆的增大变得越来越慢,特别是现在内存都越来越大了。所以为了提升性能就引入卡表。

卡表提升性能的原理:逻辑上把老年代内存分成一个个大小相等的卡页(card page,大小512byte),然后对每个卡片准备一个与其对应的标记位,并将这些位集中起管理就好像一个表格(mark table)一样,当改写对象引用是从老年代指向新生代时,在老年代对应的卡片标记位上设置标志位即可,通常这样的卡片我们称之为dirty card。这项操作可以通过上面的提到的write barrier来实现,这样就算对象跨多张卡片也不会有什么问题。卡表通常是用byte数组实现的,byte的值只能取[0,1]这两种。所以btye[i] = 1 就表示第i + 1 卡片所在内存上有指向新生代引用的老年代对象,这时只要tracing这个卡片上的对象即可。如果每个card大小的是128字节(1024位,)那卡表就只占整个老年代的1/1024之一。所以遍历卡表的时间会远比遍历整个老年代快得多!这其中背后思想就是典型以空间换时间的思路!这种思路在G1中也有体现,只不其对应的数据是remember set而已。

对于新生代:它记录老年代到新生代的引用,younggc时不用遍历整个老年代

对于老年代:它记录并发标记开始引用发生变化的card,并发标记结束后需要处理这些card

由于新生代GC与老年代GC同时使用card table,所以会出现冲突的情况

但这个card必须在remark阶段进行重新标记。所以增加了另一个数据结构mod union table解决此问题。

CMS垃圾收集器详解

mod union table是一个bit位向量,一个bit表示一个card的状态。

它由新生代垃圾收集器维护,新生代GC将card设置为clean之前,把mod union table设置为dirty。card table状态为dirty、或者mod union table标记为dirty、或者同时两种数据结构都标记为dirty的card表示并发标记阶段引用发生了变化,需要在后面的阶段进行处理。

write barrie 

write barrie写屏障类似于一个切面,用户线程写对象引用的时候就触发write barrier的逻辑,将对象所处的card设置为dirty。

并发预清理concurrent preclean

通过参数CMSPrecleaningEnabled选择关闭该阶段,默认启用,主要做两件事情:

处理dirty card,降低remark阶段暂停时间。

重新标记的过程是STW的,所以为了缩短停顿时间,在并发标记之前应该尽可能多的完成重新标记阶段的工作。并发预清理就是对dirty card进行遍历处理,降低重新标记需要处理的dirty card的数量。

可中断预清理concurrent abortable preclean

该阶段发生的前提是,新生代Eden区的内存使用量大于参数CMSScheduleRemarkEdenSizeThreshold 默认是2M,如果新生代的对象太少,就没有必要执行该阶段,直接执行重新标记阶段。

为什么需要这个阶段,存在的价值是什么?

因为CMS GC的终极目标是降低垃圾回收时的暂停时间,所以在该阶段要尽最大的努力去处理那些在并发阶段被应用线程更新的老年代对象,这样在暂停的重新标记阶段就可以少处理一些,暂停时间也会相应的降低。

在该阶段,主要循环的做两件事:

当然了,这个逻辑不会一直循环下去,打断这个循环的条件有三个:

重新标记final remark

在之前的并行阶段,可能产生新的引用关系如下:

上述对象中可能有一些已经在Precleaning阶段和AbortablePreclean阶段被处理过,但总存在没来得及处理的,所以需要做以下事情:

在第1步骤中,需要遍历新生代的全部对象,如果新生代的使用率很高,需要遍历处理的对象也很多,这对于这个阶段的总耗时来说,是个灾难(因为可能大量的对象是暂时存活的,而且这些对象也可能引用大量的老年代对象,造成很多应该回收的老年代对象而没有被回收,遍历递归的次数也增加不少),如果在这之前发生一次YGC,这样就可以避免扫描无效的对象。

CMS算法中提供了一个参数:CMSScavengeBeforeRemark,默认并没有开启,如果开启该参数,在执行该阶段之前,会强制触发一次YGC,可以减少新生代对象的遍历时间,回收的也更彻底一点。

并发清除concurrent sweep

并发清除标记为不可达的对象,回收并合并空闲内存。

并发重置concurrent reset

重新设置CMS相关的各种状态及数据结构,为下一个垃圾收集周期做好准备。

并发带来的好处是可以降低用户线程的停顿时间,对于在线服务类应用非常有益,因为长时间的停顿可能导致响应超时等问题。但相对于非并发垃圾收集器,CMS整个周期内很多工作是重复的(比如重新标记阶段对dirty card中的对象重新标记,而在并发标记阶段可能已经标记过了),导致整体的吞吐量是降低的。

浮动垃圾

内存碎片

CMS默认开启UseCMSCompactAtFullCollection 参数,在FullGC时进行内存碎片的合并整理。内存碎片虽然解决了,但负面影响就是停顿时间变长了。还有另外一个CMSFullGCsBeforeCompaction参数可以控制多少次FullGC才会进行整理,默认是0代表每次FullGC都会进行碎片整理。

运行过程常见问题

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/215199.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午2:27
下一篇 2026年3月18日 下午2:28


相关推荐

  • 背包九讲—-整理+例题[通俗易懂]

    背包九讲—-整理+例题[通俗易懂]背包九讲类型汇总:1.01背包问题2.完全背包问题3.多重背包问题4.混合背包问题5.二维费用的背包问题6.分组背包问题7.背包问题求方案数8.求背包问题的方案9.有依赖的背包问题注:以下所有题目来源于ACwing题库,链接:https://www.acwing.com/problem/这里每个类型基本都是具体题目+自己的一些体会+代码,背包九讲的理论以及解析证明之类的…

    2022年4月19日
    52
  • pycharm添加解释器失败_pycharm更新python解释器

    pycharm添加解释器失败_pycharm更新python解释器求大神告知:Pycharm添加Python解释器提示:CannotsetupapythonSDKatPython3.8(untitled5)(H:\Python\python-3.8.2-amd64.exe).TheSDKseemsinvalid.并且Python一直出现ModifySetup怎么解决?…

    2022年8月26日
    9
  • java calendar 日期实现不断加一天

    java calendar 日期实现不断加一天Calendarcc=Calendar.getInstance();//获得系统时间cc.add(cc.DATE,1);//让日子每天向后加一天 date=cc.getTime();   //这个时间就是系统时间加一天后的

    2022年5月20日
    70
  • 第二章,springboot 配置,yaml 语法[通俗易懂]

    第二章,springboot 配置,yaml 语法[通俗易懂]第二章,springboot 配置,yaml 语法

    2022年4月24日
    39
  • 在Linux安装Jenkins

    在Linux安装Jenkins自动更新发布必备神器,装起来

    2025年8月4日
    5
  • 2020美赛C题解题思路(A Wealth of Data)[通俗易懂]

    2020美赛C题解题思路(A Wealth of Data)[通俗易懂](占个坑,B题已写完并发布,现正写C题)《数学建模想获奖?国赛、美赛看这一个就够了》——数学建模的进阶指南!内容全面、门类齐全,包含组队、日常训练、算法(含MATLAB代码)、建模、写作和“高校内部培训资源”等诸多方面的指导!千载难逢、不容错过!——有条件的朋友们支持一下,谢谢!需要“数学建模国赛美赛资源包”的关注公众号“猫和真人”,回复“1”即可获得资源包,有条件的支持一下哈!…

    2022年4月29日
    51

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号