G1 GC(Garbage-First Garbage Collector)作为 JDK 9 及以上版本的默认垃圾收集器,凭借 “兼顾低延迟与吞吐量”“无内存碎片”“支持大堆内存” 的特性,成为当前生产环境中最主流的 GC 选择。但多数开发者对 G1 的认知仅停留在 “分区管理”“优先回收垃圾多的区域” 等表层概念,对其完整回收流程、核心机制的细节知之甚少。

本文将聚焦 G1 GC 的垃圾回收全过程,从内存模型、触发条件到分阶段执行逻辑,结合实战细节和参数调优,帮你彻底搞懂 G1 GC 的工作原理,解决 GC 频繁、停顿过长等线上问题。


一、G1 GC 的核心基础:打破 “代” 的 Region 内存模型

要理解 G1 的回收流程,首先要掌握其最核心的内存设计 ——Region 分区模型,这是 G1 与传统分代收集器(如 CMS、Parallel GC)最本质的区别。

1.1 Region 的基本概念

G1 将整个 Java 堆内存划分为多个大小相等的独立内存块(Region),而非固定划分年轻代(Eden/Survivor)和老年代。

  • Region 大小:由 JVM 根据堆内存总大小自动计算(范围 1MB~32MB,且为 2 的幂次方),也可通过-XX:G1HeapRegionSize手动指定;
  • Region 类型:每个 Region 可动态标记为以下类型,且类型可随 GC 过程切换:
    • Eden 区(E):存放新创建的对象;
    • Survivor 区(S):存放年轻代 GC 后存活的对象;
    • 老年代区(O):存放从年轻代晋升的长生命周期对象;
    • 大对象区(Humongous,H):存放超过单个 Region 大小的大对象(会占用连续的多个 Region),直接划入老年代管理。

1.2 为什么要设计 Region?

传统分代收集器的年轻代 / 老年代是固定大小的连续内存,回收时需扫描整个代,停顿时间随内存增大而线性增加;而 G1 的 Region 模型:

  1. 回收时只需扫描 “垃圾占比高” 的 Region,而非整个堆,大幅缩短 STW 停顿;
  2. 动态调整各类型 Region 的数量,适配业务对象的生命周期(如秒杀场景临时创建大量短生命周期对象,G1 会自动增加 Eden 区 Region 数量);
  3. 回收时通过复制算法整理 Region,天然避免内存碎片(解决 CMS 的核心痛点)。

二、G1 GC 的垃圾回收类型:Young GC + Mixed GC + Full GC

G1 的回收流程分为三种类型,不同类型触发条件、执行逻辑不同,其中Young GCMixed GC是常规回收,Full GC是异常情况(需尽量避免)。

2.1 Young GC(年轻代 GC):只回收 Eden/Survivor 区

触发条件

当 Eden 区 Region 的总内存占用达到阈值(默认约 50%),触发 Young GC,仅回收年轻代 Region(E+S),不涉及老年代。

执行流程(核心是复制算法,全程 STW)
  1. 初始准备:暂停所有用户线程(STW),启动多线程 GC 任务;
  2. 标记存活对象:通过可达性分析,标记 Eden 区和 From Survivor 区的存活对象;
  3. 复制存活对象:将存活对象复制到空的 To Survivor 区(若 To Survivor 空间不足,部分对象直接晋升到老年代 Region);
  4. 清理与切换:清空原 Eden 区和 From Survivor 区,将 To Survivor 区标记为 From Survivor 区,等待下一次 Young GC;
  5. 恢复用户线程:STW 结束,用户线程继续执行。
关键特点
  • 全程 STW,但仅回收年轻代 Region,停顿时间短(通常毫秒级);
  • 线程数:默认等于 CPU 核心数,可通过-XX:ParallelGCThreads调整;
  • 停顿目标:G1 会根据-XX:MaxGCPauseMillis(默认 200ms)自动调整每次 Young GC 回收的 Region 数量,确保停顿不超过目标值。

2.2 Mixed GC(混合 GC):回收年轻代 + 部分老年代

Mixed GC 是 G1 的核心回收流程,也是 “Garbage-First”(优先回收垃圾多的区域)的体现,触发后同时回收年轻代和部分老年代 Region。

触发条件

老年代 Region 的内存占用达到 “并发标记阈值”(默认 45%,可通过-XX:InitiatingHeapOccupancyPercent调整),触发 Mixed GC,分为并发标记阶段筛选回收阶段两大步骤。

第一步:并发标记阶段(几乎无 STW)

该阶段的核心目标是标记全堆的存活对象,并计算每个老年代 Region 的 “垃圾占比”(垃圾对象占 Region 总大小的比例),为后续筛选回收做准备。共分为 4 个子阶段:

阶段 是否 STW 核心操作 耗时 / 特点
初始标记(Initial Mark) 是(极短) 标记 GC Roots 直接引用的对象(如虚拟机栈中的对象、静态变量) 毫秒级,通常借 Young GC 的 STW 阶段完成,无额外停顿
并发标记(Concurrent Mark) GC 线程与用户线程并行执行,遍历初始标记对象的引用链,标记全堆存活对象;同时计算每个老年代 Region 的垃圾占比 耗时最长(秒级),但不影响用户线程
最终标记(Final Mark) 是(短) 修正并发标记期间用户线程修改引用链导致的 “标记偏差”(如对象被创建 / 销毁、引用被修改) 停顿时间比初始标记长,但远短于传统 Full GC
清理筛选(Cleanup) 筛选出垃圾占比最高的老年代 Region(优先回收列表),并清空无存活对象的 Region 无 STW,为后续筛选回收做准备
第二步:筛选回收阶段(STW)

该阶段是 Mixed GC 的核心,体现 “Garbage-First” 的设计思想,全程 STW:

  1. 选择回收 Region:从 “优先回收列表” 中选择垃圾占比最高的一批 Region(包括年轻代 Region + 老年代 Region),数量由MaxGCPauseMillis控制(确保停顿不超目标);
  2. 复制存活对象:将选中 Region 中的存活对象复制到空 Region(年轻代存活对象复制到新 Survivor 区,老年代存活对象复制到新老年代区);
  3. 清理 Region:清空原 Region,标记为空闲,供后续对象分配使用;
  4. 恢复用户线程:STW 结束。
关键特点
  1. 每次 Mixed GC 仅回收 “垃圾占比高” 的老年代 Region(通常分批回收,而非一次回收所有),避免长时间 STW;
  2. 复制过程天然整理内存,无碎片;
  3. 并发标记阶段占用 CPU 资源(默认 GC 线程数为 CPU 核心数的 1/4),高 CPU 负载场景需调整线程数(-XX:ConcGCThreads)。

2.3 Full GC(全堆 GC):G1 的 “兜底” 回收

Full GC 是 G1 尽量避免的回收方式,触发后会退化为串行收集器(Serial Old GC),全程 STW 且扫描整个堆,停顿时间极长(秒级甚至分钟级)。

触发条件
  1. Mixed GC 回收速度跟不上对象创建速度(如老年代内存快速占满);
  2. 大对象分配时无足够连续 Region;
  3. 并发标记阶段出现内存不足;
  4. JVM 参数配置错误(如MaxGCPauseMillis设置过小,导致 G1 频繁回收,反而触发 Full GC)。
解决思路

Full GC 是线上问题的 “重灾区”,核心解决方法:

  1. 调整InitiatingHeapOccupancyPercent(降低阈值,让 Mixed GC 提前触发);
  2. 增大堆内存,避免内存不足;
  3. 优化代码,减少大对象创建;
  4. 调整MaxGCPauseMillis(不要设置过小,建议 100~300ms)。

三、G1 GC 回收流程全解析:从触发到完成的完整链路

结合上述回收类型和阶段,我们用流程图梳理 G1 GC 的完整执行链路,并标注关键控制点:

核心细节补充

  1. 停顿时间控制:G1 在每次回收前会预测 “回收哪些 Region 能在MaxGCPauseMillis内完成”,若预测停顿超时,会减少本次回收的 Region 数量;
  2. Remembered Set(RS):G1 为每个 Region 维护 RS,记录其他 Region 对当前 Region 对象的引用,避免全堆扫描(这是 G1 能高效标记的关键);
  3. 卡表(Card Table):RS 的底层实现,将每个 Region 划分为 512 字节的 “卡”,当对象引用更新时,标记对应卡为 “脏卡”,并发标记时仅扫描脏卡,提升效率。

四、G1 GC 实战调优:核心参数与问题排查

理解回收流程后,结合线上常见问题,给出核心调优参数和排查思路。

4.1 核心调优参数

参数 作用 推荐值
-XX:+UseG1GC 启用 G1 GC 必设(JDK9 + 默认,JDK8 需手动设置)
-XX:MaxGCPauseMillis 设置 GC 最大停顿时间目标 100~300ms(不要过小,否则会导致 GC 频繁)
-XX:G1HeapRegionSize 指定 Region 大小 自动计算即可,无需手动设置
-XX:InitiatingHeapOccupancyPercent 触发 Mixed GC 的老年代占用阈值 40~50(默认 45,内存紧张时设为 40)
-XX:ParallelGCThreads Young GC/Mixed GC STW 阶段的线程数 等于 CPU 核心数(如 8 核设为 8)
-XX:ConcGCThreads 并发标记阶段的线程数 CPU 核心数 / 4(如 8 核设为 2)
-XX:G1NewSizePercent 年轻代 Region 最小占比 5(默认 5,可根据业务调整)
-XX:G1MaxNewSizePercent 年轻代 Region 最大占比 60(默认 60,短生命周期对象多的场景可提高)

4.2 线上常见问题排查

问题 1:Young GC 频繁触发
  • 原因:Eden 区 Region 过小,对象创建速度快;
  • 解决:提高G1MaxNewSizePercent(如设为 70),增加年轻代 Region 数量;优化代码,减少临时对象创建。
问题 2:Mixed GC 停顿时间过长
  • 原因:单次回收的老年代 Region 过多,或 RS / 卡表扫描耗时久;
  • 解决:降低MaxGCPauseMillis(如从 200ms 改为 100ms),减少单次回收的 Region 数量;检查是否有大量对象引用更新(如频繁修改集合),优化代码。
问题 3:频繁触发 Full GC
  • 原因:Mixed GC 触发过晚,老年代内存不足;
  • 解决:降低InitiatingHeapOccupancyPercent(如从 45 改为 40),让 Mixed GC 提前触发;增大堆内存(-Xmx)。

4.3 GC 日志分析关键指标

开启 G1 GC 日志(-Xlog:gc*:file=gc.log:time,level,tags)后,重点关注以下指标:

[GC pause (G1 Young Generation) (young) 200M->100M(1024M), 0.010s]
[GC pause (G1 Mixed Generation) (mixed) 500M->200M(1024M), 0.050s]
  • 第一行:Young GC,停顿时间 0.010s(10ms),堆内存从 200M 降到 100M;
  • 第二行:Mixed GC,停顿时间 0.050s(50ms),符合MaxGCPauseMillis=200ms的目标;
  • 若出现[Full GC (G1 Full GC)],需立即排查内存不足或参数配置问题。

五、总结

G1 GC 的回收流程核心是 “分区管理 + 优先回收高垃圾占比 Region + 并发标记 + 复制整理”,总结关键要点:

  1. 内存模型:以 Region 为基本单位,动态划分年轻代 / 老年代,打破传统分代的固定边界;
  2. 回收类型:常规回收为 Young GC(仅年轻代,STW)和 Mixed GC(年轻代 + 部分老年代,低 STW),Full GC 为异常情况需避免;
  3. 核心优势:通过预测停顿时间、选择性回收 Region,兼顾低延迟和吞吐量,且无内存碎片;
  4. 调优核心:围绕MaxGCPauseMillisInitiatingHeapOccupancyPercent调整,避免 Full GC 触发。

掌握 G1 GC 的回收流程,不仅能帮你理解 GC 日志、解决线上 GC 问题,更能从内存角度优化代码(如减少大对象、临时对象创建),提升 Java 应用的稳定性和性能。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐