基于本文回答
0
评论

G1 垃圾收集器的工作原理

知识点图片

G1(Garbage-First)垃圾收集器是 Java 虚拟机(JVM)中一款非常重要的垃圾收集器,从 JDK 9 开始成为默认的垃圾收集器。它主要面向大内存、多处理器的服务器环境,其核心设计目标是在保证高吞吐量的同时,实现可预测的垃圾回收停顿时间(Pause Time)

为了理解 G1 的工作原理,我们需要从它的内存布局核心数据结构以及垃圾回收的过程三个方面来剖析。


一、 颠覆性的内存布局:Region (区域)

在 G1 之前的收集器(如 CMS、Parallel),堆内存被物理划分为连续的年轻代(Young Gen)和老年代(Old Gen)。
G1 打破了这种物理边界,它将整个 Java 堆划分为多个大小相等的独立区域(Region)(通常大小为 1MB - 32MB,最多约 2048 个)。

虽然物理上不连续,但 G1 仍然保留了逻辑上的分代概念。每个 Region 都可以动态地扮演以下角色之一:

  1. Eden 空间 (E):存放新创建的对象。
  2. Survivor 空间 (S):存放经过一次或多次 Young GC 后仍然存活的对象。
  3. Old 空间 (O):存放长时间存活的对象。
  4. Humongous 空间 (H)这是 G1 独有的。用于专门存储巨型对象(大小超过 Region 容量的 50% 的对象)。如果一个对象极大,可能会连续占用多个 H 区。G1 会把 H 区当作老年代来看待。

好处: 内存分配和回收的粒度变小了,G1 可以灵活地决定每次回收多少个 Region,从而控制停顿时间。


二、 两个核心数据结构:RSet 与 CSet

为了实现局部的区域回收,G1 引入了两个重要的数据结构:

  1. RSet (Remembered Set,记忆集)

    • 作用: 记录“谁引用了我这个 Region 里的对象”
    • 原理: 每个 Region 都有一个 RSet。当老年代的对象引用了年轻代的对象时,年轻代的 RSet 会记录下这个老年代的引用。
    • 意义: 在进行年轻代回收(Young GC)时,JVM 不需要扫描整个老年代来寻找 GC Roots,只需要扫描年轻代各个 Region 的 RSet 即可,极大地提升了回收效率。
  2. CSet (Collection Set,收集集合)

    • 作用: 记录“本次 GC 需要被回收的 Region 集合”
    • 原理: 在 GC 发生时,G1 会根据用户设置的期望停顿时间-XX:MaxGCPauseMillis),计算并选择那些“回收价值最高”(即垃圾最多)的 Region 加入到 CSet 中。
    • 意义: 这是 G1 名字的由来——Garbage-First(垃圾优先),它优先回收垃圾最多的 Region。

三、 G1 的三种垃圾回收模式

G1 的运行过程不是单一的,它会在以下三种模式之间动态切换:

1. Young GC (年轻代回收)

当所有的 Eden 区都被填满时,会触发 Young GC。

  • 过程: 这是一个 STW (Stop-The-World,完全暂停用户线程) 的过程。G1 会将 Eden 区和现有的 Survivor 区中存活的对象,复制(Copying)到一个或多个新的 Survivor 区。如果对象存活时间达到了阈值,则直接晋升到 Old 区。
  • 清理: 清理完后,原来的 Eden 区被清空,重新变回空闲的 Region。

2. Concurrent Marking Cycle (并发标记周期)

当整个堆的内存使用率达到设定的阈值(默认是 45%,由 -XX:InitiatingHeapOccupancyPercent 控制)时,会触发并发标记周期。它分为五个阶段:

  • ① 初始标记 (Initial Mark): STW。借用 Young GC 的停顿时间顺便完成,仅仅标记直接与 GC Roots 相连的对象,速度极快。
  • ② 根区域扫描 (Root Region Scan): 并发。扫描 Survivor 区中引用了老年代对象的引用。
  • ③ 并发标记 (Concurrent Mark): 并发。扫描整个堆,追踪对象图,标记所有存活的对象。耗时最长,但与用户线程同时运行。
  • ④ 最终标记/重新标记 (Remark): STW。处理在并发标记期间由于用户线程运行而导致引用关系发生变化的那些对象(使用 SATB 算法,下文详解)。
  • ⑤ 清理 (Cleanup): STW + 并发。统计各个 Region 的存活对象数量和垃圾比例,完全为空的 Region 会被直接回收。对各个 Region 按照回收价值进行排序,为接下来的 Mixed GC 做准备。

3. Mixed GC (混合回收)

并发标记周期结束后,G1 已经知道了哪些老年代 Region 的垃圾最多。接下来就会发生多次 Mixed GC。

  • 回收范围: 所有的年轻代 Region + 部分收益最高的老年代 Region
  • 过程: STW。G1 会根据用户设置的预期停顿时间,从老年代中挑选出 CSet,然后采用复制算法,将存活对象复制到空闲的 Region 中,最后清理掉原来的 Region。

备用机制:Full GC (完全回收)
如果对象内存分配速度过快,Mixed GC 来不及回收,导致老年代被填满,G1 就会退化成 Full GC。这会停止所有的用户线程,使用单线程(JDK 10 之后优化为多线程)对整个堆进行全面的标记整理,停顿时间非常长,是 G1 极力要避免的情况


四、 G1 的核心算法:SATB (Snapshot-At-The-Beginning)

在“并发标记”阶段,用户线程依然在运行,这可能导致一个问题:原本存活的对象被断开了引用,或者新创建了对象,如何保证不漏标或错标?

G1 采用了 SATB(起始快照) 算法:

  • 在并发标记开始的那一刻,G1 会在逻辑上对整个堆拍一个“快照”。
  • 它假定在这个快照那一刻存在的所有对象都是存活的。
  • 如果在并发标记期间,用户线程修改了某个引用(比如把 A.b = C 改成了 A.b = null),G1 会通过写屏障(Write Barrier)把旧的引用(C)记录下来,放入一个队列中。
  • 在“最终标记 (Remark)”阶段,G1 会检查这个队列,把被删除了引用的对象当作存活对象处理(即使它实际上可能已经是垃圾了)。
  • 代价: 可能会产生一部分“浮动垃圾”(本次 GC 没回收掉,下次再回收),但这保证了标记过程的高效和正确性,极大地缩短了 STW 时间。

五、 总结与优势

为什么叫 Garbage-First?
因为它在并发标记后,会计算每个 Region 里的垃圾比例。在执行 Mixed GC 时,它会优先收集那些垃圾最多、回收能释放最大空间的 Region

G1 的主要优势:

  1. 可预测的停顿模型: 用户可以通过 -XX:MaxGCPauseMillis(默认 200ms)指定期望的停顿时间,G1 会尽量通过调整每次回收的 Region 数量来满足这个目标。
  2. 无内存碎片: G1 的整体运作基于标记-整理(Mark-Compact),局部(Region 之间)基于复制(Copying)算法。回收后会提供规整的可用内存。
  3. 大内存友好: 非常适合 8GB 甚至数百 GB 的大堆内存应用。

G1 的调优建议:
G1 的设计理念是“开箱即用”。通常只需要设置最大堆内存(-Xmx)和期望停顿时间(-XX:MaxGCPauseMillis),G1 就会自动动态调整年轻代/老年代的比例。切忌不要使用 -Xmn 强行设置年轻代大小,否则会打破 G1 控制停顿时间的机制。

右滑查看面试常问