万字概览 Java 虚拟机 (6)

除了 YGC 和 Minor GC 的意义是在业界达成了一致,都表示对 Young 区的回收,Major GC 和 Full GC 的定义都不确定,包括 JVM 规范也没有明确定义。Major GC 有时候也可以看做是 Full GC,因为 Major GC 很多时候会触发一次 Minor GC。

CMS GC

CMS GC 从 JDK1.4 引入,经过多个版本的不断发展和增强,最终在 Java9 中被标记为 Deprecated。这个 GC 适用于对停顿时间非常敏感的前端系统,目的是以较短的停顿尽快回收垃圾。CMS GC 之所以称作「几乎完全并发的垃圾回收器」是因为在整个 GC 过程中仍然存在 2 处 STW(Stop The World),也就是全局暂停,用户线程也不能执行。

CMS 的工作流程

CMS 主要由四个步骤构成:

初始标记
STW 阶段,寻找 GC Roots

并发标记
和用户线程并发执行,这个阶段会根据 GC Roots 标记对象

最终标记
并发标记过程中客户线程的运行导致引用发生变动,这一步对并发标记阶段中不可达对象在最终标记阶段变成可达对象的情况进行修正。如果并发标记阶段新产生的不可达对象,且没有在并发标记阶段被标记的,成为「浮动垃圾」无法回收,等待下一次 CMS GC。

并发清除
和用户线程并发执行,将标记的死亡对象进行清理

一个 CMS GC 会增长两次 jstat 中 FGC 的值,也就是说,FGC 记录的是 GC STW 的次数,而不是一次对整个堆的完整收集。

-XX:+UseConcMarkSweepGC

这个参数表示启用 CMS GC,默认会在 Young 区启用 ParNew GC。从官方对参数的描述来看,CMS GC 确实是一个应用于 Old 区的 GC。

product(bool, UseConcMarkSweepGC, false, \ "Use Concurrent Mark-Sweep GC in the old generation")

JVM 默认启动 ParNew GC 的逻辑:

// Set per-collector flags if (UseParallelGC || UseParallelOldGC) { set_parallel_gc_flags(); } else if (UseConcMarkSweepGC) { // Should be done before ParNew check below set_cms_and_parnew_gc_flags(); } else if (UseParNewGC) { // Skipped if CMS is set above set_parnew_gc_flags(); } else if (UseG1GC) { set_g1_gc_flags(); } -XX:CMSInitiatingOccupancyFraction

这个参数用于设置当 Old 区使用率达到多少时应该发生一次 CMS GC,但是要注意是「应该发生」而不是「一定发生」,也就是说即使 Old 区使用率达到了指定阈值也可能不会触发 CMS GC。因为 CMS GC 的触发是由其扫描 Old 区使用量的线程控制的,只有当线程扫描到使用率达到阈值时才发触发 CMS GC。这个线程每 2 秒对 Old 区使用率进行一次计算,如果超过指定值则启动 CMS GC。这个扫描频率可以使用参数 -XX:CMSWaitDuration 指定。

这个参与必须要和 -XX:+UseCMSInitiatingOccupancyOnly 参数配合使用,只有开启了这个参数,自定义配置才起作用,否则会使用 JVM 的默认值,通常是 92%。

-XX:+CMSScavengeBeforeRemark

这个参数表示 CMS GC 在执行最终标记(重新标记)时先「尝试」执行一次 Young GC。

最终标记时涉及整个堆,包括 Young 区和 Old 区。扫描 Young 区是因为如果 Young 区对象引用了 Old 区对象,那么 Old 区的对象就是存活对象,Young 区对象就是 Old GC 的 GC Roots。

在最终标记前进行一次 YGC 可以大量减少 Young 区对象,那么需要扫描的 Young 区对象数量就变少了,从而最终标记的速度也就提高了。

但是有利就有弊,如果运气不好遇到 Young 区对象存活率极高或者 Young 区对象很少的情况,这次 YGC 的意义就不大了。

JVM 对跨代引用的处理

YGC 的目的是回收掉 Young 区中不再存活的对象,如果 Young 区中的对象直接就在 Tracing Chain 上自然是不可回收,但是 Old 区也有可能引用了 Young 区对象,这时候为了确保 Young 区中的对象不被错误回收,最直接的办法就是扫描整个 Old 区的对象,看他们引用了哪些 Young 区对象。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygjjy.html