1. 引言

java 优于 c++ 的一个亮点就是自动的垃圾回收机制,成也萧何败萧何,最困扰 java 程序员的问题往往又都和垃圾回收机制有关,作为一个 java 程序员,如果你不了解 java 垃圾回收的机制,那么你时刻都可能面临性能的瓶颈,甚至遇到种种诡异的问题而无从下手。

此前,我们已经介绍过 java 垃圾回收相关的很多内容:

HotSpot 提供的垃圾收集器

本文我们就来详细介绍一个 java 中明星级的垃圾回收器 CMS 的回收机制,而新一代的 G1 回收器的执行机制会在下一篇文章中再来介绍,敬请期待。

2. CMS 垃圾收集器概览

CMS 是 Concurrent Mark Sweep 的缩写,是现在主流十分常用的老年代垃圾回收器,他的主要目标是用最短的回收停顿时间实现老年代的清理,因此,对于强调用户体验的互联网应用来说,CMS 成为了一个首选。

那么,CMS 垃圾回收器究竟是怎么工作的呢?

CMS 垃圾回收器采用了标记清除算法来实现。

3. 三色标记法

说到“标记-清除”算法,顾名思义,jvm 将整个收集过程划分为了“标记”与“清除”两大步骤来实现。

具体到标记流程,jvm 采用了“三色标记法”,就是将不同的对象划分为三大类:

  1. 黑色对象 -- 已标记,内部引用已经都被处理
  2. 白色对象 -- 未被标记对象
  3. 灰色对象 -- 已被标记,但内部引用未被处理

我们知道,jvm 是通过可达性分析法来实现标记过程的,jvm 从 GC Root 开始,逐步遍历引用树来实现对内存区域内每个对象的标记,可以参看:

java 对象存活判定算法

4. CMS 的执行步骤

一般来说,人们通常把 CMS 的执行过程分为四个步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

加上可选的阶段,CMS 回收的过程可以划分为七个步骤,下面我们就来详细介绍:

  1. 初始标记
  2. 并发标记
  3. 并发预清理
  4. 可中断的并发预清理
  5. 重新标记
  6. 并发清除
  7. 并发重置

4.1 初始标记

如上所述,初始标记最重要的工作就是以 GC-Roots 为起点进行可达性分析,标记出所有当前活跃的也就是被引用的对象。

但是,除了被 GC-Root 引用外,老年代中的对象如果仅被年轻代中的对象引用,他也是不能回收的,因此,在上述以 GC-Roots 为起点进行的标记完成后,还需要遍历新生代对象,标记可达的老年代对象。

由于上述两个过程仅仅遍历被 GC-Roots 和新生代对象直接引用的老年代对象,执行起来速度会非常快,为了避免在标记过程中对象引用的动态变化,在初始标记阶段,程序需要进行短暂停止,这称为“Stop The World”。

jdk8 以后,初始标记支持多线程标记,可以通过 CMSParallelInitialMarkEnabled 参数开启。

4.2 并发标记

“并发标记”阶段所谓的“并发”,指的就是用户线程可以与回收线程同步执行,而不需要让用户线程暂停,从而保证了用户线程的执行效率。

初始标记的过程中,为了尽量缩短用户线程的暂停时间,所以仅对被 GC-Roots 和新生代对象直接引用的老年代对象进行了标记,所以,接下来的一项工作就是对 GC-Roots 和新生代对象间接引用的老年代对象进行标记,这就是并发标记阶段的主要工作了。

4.2.1 如何处理并发标记阶段的引用关系变更

由于在并发标记阶段,用户线程与回收线程并发执行,随时可能有新生代的对象晋升到老年代、直接在老年代分配对象或者引用关系发生变更等等情况发生,这些情况下,jvm 是否必须扫描整个老年代才能够识别出这些发生了变化的对象呢?

答案当然是否定的,HotSpot 使用了一套名叫“卡表”(Card Table)的数据结构来管理老年代的内存,Card Table 实际上就是一个索引列表,每一个表项占用 1 byte 空间,索引内存中占 512 byte 的页空间。

而在对每一个对象引用进行写操作之前和之后 HotSpot 都附加了一定的逻辑,称之为“写屏障”(Write Barrier),写屏障会在一个对象的引用发生变化时,将该对象所在的页在卡表中对应的表项标记为 dirty。

但是,由于 jvm 中对象引用变更是极为频繁的,反复读写卡页显然会有很大的性能开销,于是,从 JDK7 开始,HotSpot 引入了一个新的 JVM 参数 -XX:+UseCondCardMark,从而让已经标记过的卡页不再重复标记。

4.3 并发预清理

这一步是可选的,可以通过参数 CMSPrecleaningEnabled 参数可以启用或关闭该阶段,默认是开启的。

上文已经介绍到,在并发标记阶段,由于引用的变更,可能会产生一些 dirty page,这一阶段的主要工作就就是处理这些脏页,虽然在后面的重新标记阶段也拥有处理脏页的逻辑,但重新标记阶段会 Stop The World,所以这一阶段的核心仍然是让停顿时间尽量缩短。

4.4 可中断的并发预清理

这一阶段的主要工作是处理新生代指向老年代的新引用,从而让老年代的一些未被标记的对象成为活跃对象。

同样,在重新标记阶段也会处理这样的情况,这一阶段仍然是为了缩短停顿时间而进行的。

CMS 对于该阶段有以下 4 个参数:

  1. CMSScheduleRemarkEdenSizeThreshold -- 当 eden 区空间超过该值时才执行该阶段,默认为 2M
  2. CMSScheduleRemarkEdenPenetration -- 当 eden 空间使用率大于该值时,中断该阶段的执行,默认为 50%
  3. CMSMaxAbortablePrecleanTime -- 该阶段的最长执行时间,默认为 5s
  4. CMSScavengeBeforeRemark -- 强制在该阶段进行一次 minorGC

显然,在这一阶段中要识别新生代对象对老年代对象的新引用,那么就必须扫描整个新生代,这显然是一项很耗时的操作,但由于新生代的对象大多是朝生夕死的,所以如果在一次 minorGC 之后紧接着进行一次预清理,新生代中需要扫描的对象就会所剩无几了。

CMS 通过 CMSScavengeBeforeRemark 参数强制在可中断的并发预清理阶段执行一次 minorGC,虽然 minorGC 也会让用户线程短暂停顿,但这样可以缩短下一阶段的停顿时间,整体上还是利大于弊的。

4.5 重新标记

在上述的并发过程中,用户线程始终在执行,因此随时可能会产生引用变更,比如:

  1. 老年代未标记对象被新生代对象引用
  2. 新生代对象晋升到老年代
  3. 已标记对象增加了对未标记对象的引用
  4. 已标记对象的引用被删除

这些情况下,很有可能造成标记数据的不准确,如果直接进行清理,就有可能有误清理的情况发生,因此,jvm 需要再一次 Stop The World 来进行重新标记,从而保证在真正的清理前,标记的准确性。

重新标记阶段,jvm 主要进行以下三个工作:

  1. 遍历新生代对象,重新标记被引用的老年代对象
  2. 从 GC Roots 出发,重新标记被引用的老年代对象
  3. 遍历卡表,对脏页内的老年代对象进行重新标记

但由于有了前面三个步骤的反复标记过程,重新标记阶段的工作量已经被大大降低,停顿时间当然也因此大大减少。

4.6 并发清除

完成了标记工作,就只剩下最后的一步工作,那就是清除了。

由于被清除的对象都是未被使用的对象,因此在清楚操作进行中,是不需要 Stop The World 的,这一步操作也是和用户线程同步执行的。

4.7 并发重置

完成了整个 CMS 的标记-清除工作后,需要将 CMS 算法的内部数据进行重置,从而让下一次 GC 顺利开始。

5. 示例:GC 日志

下面是一次 CMS 进行 Full GC 的完整日志,我们可以清楚的看到每一阶段的运行信息与耗时情况:

2021-01-17T12:52:41.268+0800: 463.575: Total time for which application threads were stopped: 0.4222317 seconds, Stopping threads took: 0.0001890 seconds2021-01-17T12:52:41.268+0800: 463.575: Application time: 0.0000643 seconds // 初始标记(Initial Mark)2021-01-17T12:52:41.271+0800: 463.578: [GC (CMS Initial Mark) [1 CMS-initial-mark: 1777676K(3145728K)] 1987618K(5033216K), 0.0327958 secs] [Times: user=0.12 sys=0.00, real=0.04 secs] // 并发标记(Concurrent Mark)2021-01-17T12:52:41.304+0800: 463.611: [CMS-concurrent-mark-start]2021-01-17T12:52:42.050+0800: 464.356: [CMS-concurrent-mark: 0.746/0.746 secs] [Times: user=2.61 sys=0.21, real=0.74 secs] // 并发预清理(Concurrent Preclean)2021-01-17T12:52:42.050+0800: 464.356: [CMS-concurrent-preclean-start]2021-01-17T12:52:42.171+0800: 464.477: [CMS-concurrent-preclean: 0.056/0.121 secs] [Times: user=0.47 sys=0.05, real=0.12 secs] // 并发可取消的预清理(Concurrent Abortable Preclean)2021-01-17T12:52:42.171+0800: 464.477: [CMS-concurrent-abortable-preclean-start]2021-01-17T12:52:43.753+0800: 466.059: [CMS-concurrent-abortable-preclean: 1.128/1.582 secs] [Times: user=4.86 sys=0.41, real=1.59 secs] // 最终标记(Final Remark)2021-01-17T12:52:43.756+0800: 466.063: [GC (CMS Final Remark) [YG occupancy: 1414648 K (1887488 K)]466.063: [Rescan (parallel) , 0.2583497 secs]466.321:[weak refs processing, 0.0005512 secs]466.322: [class unloading, 0.0514608 secs]466.373: [scrub symbol table, 0.0122328 secs]466.385: [scrub string table, 0.0017416 secs][1 CMS-remark: 1974197K(3145728K)] 3388846K(5033216K), 0.3265593 secs] [Times: user=0.83 sys=0.00, real=0.33 secs] // 并发清除(Concurrent Sweep)2021-01-17T12:52:44.083+0800: 466.389: [CMS-concurrent-sweep-start]2021-01-17T12:52:48.578+0800: 470.885: [CMS-concurrent-sweep: 3.224/4.495 secs] [Times: user=14.43 sys=1.19, real=4.49 secs] // 并发重置(Concurrent Reset)2021-01-17T12:52:48.638+0800: 470.945: [CMS-concurrent-reset-start]2021-01-17T12:52:48.647+0800: 470.953: [CMS-concurrent-reset: 0.008/0.008 secs] [Times: user=0.06 sys=0.01, real=0.01 secs]

CMS 执行的七个阶段相关推荐

  1. [机缘参悟-73]:深度思考:心智提升的七个阶段

    目录 一.何谓心智 二.人的心智发展阶段 第一阶段:无自我觉察 - 无知.情绪化 第二阶段:自我反思 - (觉知.自省) 第三阶段:掌控情绪 - (理性.思考) 第四阶段:知行合一(闭环.复盘.持续改 ...

  2. KVM学习及应用的七个阶段

    传说中,收集7颗龙珠,就可以实现一个愿望. KVM虚拟化的学习,也可以分为七个阶段,经过七个阶段的学习,就在生产环境中完成虚拟化任务. 第一阶段 虚拟机管理学习 1 了解虚拟化的历史及概念,Xen V ...

  3. 企业会计是怎么做的?企业会计做账通常分为七个阶段

    企业会计是怎么做的?企业会计做账通常分为七个阶段,会计是每一家企业成立后都必须做的事情,即使是注册的香港企业,在免税期过后也需要做账.会计对很多人来说都很陌生,主要是因为它要求很高的专业性,同时也决定 ...

  4. 人工智能在未来将要经历的七个阶段

    到2030年,全球GDP将在人工智能的作用下增长15.7万亿美元.有84%的企业认为人工智能可以为其带来竞争优势,因为它会让企业生产率提高40%.那么人工智能在未来将如何发展以及会经历哪些阶段? 1. ...

  5. Mysql 慢查询 Sql执行计划 SQL每阶段的耗时

    文章目录 前言 一.慢查询的相关参数 slow_query_log 是否开启了慢查询 开启慢查询 slow_query_log_file 指定慢查询日志的存储路径及文件 long_query_time ...

  6. 中国人英语学习的七个阶段

    第一阶段:婴儿阶段 听说读写全不会,只会背26个英文字母 第二阶段:只言片语阶段 简单的几个单词短语,此时遇见老外一般除了Hello, Good Morning之外基本就是哑巴,基本上是我初中时候的情 ...

  7. 从计算机视觉的小白变为大神,你需要经历这七个阶段

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 如果想要机器能够进行思考,我们需要先教会它们去看.  李飞飞--D ...

  8. php伪协议实现命令执行的七种姿势

    文件包含函数:include.require.include_once.require_once.highlight_file .show_source .readfile .file_get_con ...

  9. android窗口动画体系,Android 7.1 GUI系统-窗口管理WMS-动画的执行(七)

    前面只是动画资源的加载过程,下面看下动画是怎么执行起来的? 前面在分析窗口申请的过程中,分析过relayoutWindow中的调用performSurfacePlacement,在这个函数的最后调用了 ...

最新文章

  1. 【Airtest】Airtest中swipe方法兼容不同分辨率的解决方法
  2. win11频繁更新,关闭win11恶意软件删除工具补丁更新
  3. Python学习笔记:错误,测试,调试(起)
  4. Docker手动构建 nginx+py3+uwsgi环境
  5. 实例1.2:获得应用程序主窗口指针
  6. Nginx windows安装部署
  7. [C语言] 插入排序之直接插入排序的特性及实现
  8. Android:数据持久化、Environment、SharedPreferences、内部存储internalStorage
  9. 客户端中转request请求乱码
  10. td.moveRow方法
  11. SpringBoot06 统一响应格式
  12. 固态硬盘开卡软件_SATAFIRM S11 MSATA固态硬盘刷固件
  13. ndk读取assets文件
  14. HCNP-Cloud云计算认证
  15. win10 删除文件好慢解决方法
  16. 计算机视觉最全专栏教程总结
  17. 论文笔记:m6Acorr: an online tool for the correction and comparison of m6A methylation profiles
  18. Uniforms(uniform变量)
  19. SQL SERVER中的OLEDB等待事件
  20. matlab滤波实验,Refined Lee滤波实验MATLAB程序.docx

热门文章

  1. 爱普生R330打印机 win10下 打印照片出现打一半的情况
  2. 如果有一天我老无所依,请把我埋在,新疆的田野上
  3. python海龟教程_Python 零基础 快速入门 趣味教程 (咪博士 海龟绘图 turtle) 7. 条件循环...
  4. 5G LTE窄带物联网(NB-IoT) 9
  5. android 听筒模式外放模式的切换,YY项目之Android 听筒 扬声器 切换
  6. 宠物粮竞争后半场:国牌与洋牌的「信任保卫战」
  7. matlab pid buck,基于MATLAB的BUCK电路设计与PID闭环仿真
  8. Tobii:眼球追踪技术能够提升制造行业安全性
  9. Python获取拥有100万+关注的CSDN公众数据, 发现最关心的竟不是技术,而是…
  10. 银行都是用什么技术做超级App