G1收集器

在 Java 垃圾回收(GC)的演进史上,G1(Garbage-First)收集器是一座里程碑式的存在。它由 HotSpot 团队历时多年研发,于 JDK7u4 中首次可用,JDK9 正式取代 CMS 成为默认垃圾收集器,并在 JDK10、11 中持续优化,最终成为 JDK14 后唯一推荐的老年代收集器(CMS 被移除)。

G1 的核心设计理念是 "垃圾优先"------ 优先回收垃圾最多、回收收益最高的内存区域,同时兼顾 "低停顿" 与 "高吞吐量",完美解决了 CMS 收集器的内存碎片、并发模式失败等痛点,成为大内存、高并发场景(如微服务、分布式系统)的首选。本文将从原理到实战,全面拆解 G1 收集器,结合通俗比喻与专业细节,让你彻底掌握这一核心 GC 技术。

一、G1 核心定位:什么是 G1 收集器?

1. 核心定义

G1 是 HotSpot 虚拟机中一款面向整堆的垃圾收集器 ,基于 "分代收集" 理论与 "区域化内存布局" 设计,融合了 "标记 - 复制" 与 "标记 - 整理" 算法的优点,核心目标是可预测的低停顿------ 允许开发者通过参数指定 GC 的最大停顿时间,G1 会自动调整回收策略,确保停顿时间不超标。

2. 通俗比喻

把 JVM 堆内存比作多个独立的 "小区"(Region) ,每个小区里既有 "新住户"(新生代对象)也有 "老住户"(老年代对象),GC 线程是垃圾分类回收团队

  • 其他收集器(如 CMS):按 "新生代 / 老年代" 整体回收,要么只清理新小区,要么只清理老小区,容易造成长时间停顿;
  • G1 收集器:先统计每个小区的垃圾量(回收收益),优先清理垃圾最多、清理最快的小区,同时严格控制每次清理的小区数量,确保总停顿时间不超过预设阈值 ------ 就像回收团队先挑 "垃圾多、清理快" 的小区下手,既保证回收效率,又不影响居民(用户线程)正常生活。

3. 适用场景与核心优势

(1)适用场景
  • 大内存场景:堆内存≥4G(推荐 8G~64G),G1 的分区设计能更好地利用多核 CPU;
  • 响应时间优先的服务:如 Web 应用、微服务、电商核心系统,要求 GC 停顿时间控制在 100~200ms 内;
  • 混合场景:既需要低停顿,又不能牺牲过多吞吐量(如兼顾用户体验与系统处理能力)。
(2)核心优势(对比 CMS)
优势 具体说明
无内存碎片 基于 "标记 - 整理"+"复制" 算法,回收后内存连续,无需担心大对象分配失败
停顿可预测 支持通过-XX:MaxGCPauseMillis指定最大停顿时间,G1 自动适配
整堆回收能力 无需搭配其他收集器,可独立管理新生代和老年代,简化配置
抗并发模式失败 分区设计 + 动态调整回收区域,大幅降低 "并发模式失败" 风险
大内存高效 堆内存越大,G1 的分区优势越明显,而 CMS 在大内存下性能退化严重

二、G1 核心原理:区域化内存布局 + 分代收集

1. 内存布局:打破 "物理分代",改为 "逻辑分代 + 物理分区"

这是 G1 与传统收集器(如 CMS、Parallel Old)的核心区别 ------G1 不再将堆内存物理划分为 "新生代 + 老年代",而是将整个堆划分为多个大小相等的独立区域(Region) ,每个 Region 的大小固定(1MB~32MB,需为 2 的幂次方),可通过-XX:G1HeapRegionSize参数设置(默认由 JVM 根据堆大小自动计算)。

(1)Region 的四种类型

每个 Region 在运行时扮演不同角色,逻辑上归属于新生代或老年代,物理上是独立的内存块:

  • Eden Region(E 区):新生代区域,存放新创建的对象,对应传统收集器的 Eden 区;
  • Survivor Region(S 区):新生代区域,存放 Minor GC 后存活的对象,对应传统收集器的 Survivor 区(S0/S1);
  • Old Region(O 区):老年代区域,存放长期存活的对象,对应传统收集器的老年代;
  • Humongous Region(H 区):巨型对象区域,存放大小超过 Region 一半的大对象(如超大数组、大集合),直接分配在 H 区,避免占用多个普通 Region 导致内存碎片。
(2)分代逻辑
  • 新生代(逻辑):由多个连续或不连续的 Eden Region 和 Survivor Region 组成,默认占堆内存的 5%(可通过-XX:G1NewSizePercent调整初始占比,最大不超过 60%,由-XX:G1MaxNewSizePercent控制);
  • 老年代(逻辑):由多个 Old Region 和 Humongous Region 组成,占堆内存的剩余部分;
  • 动态调整:G1 会根据应用运行情况,动态调整新生代和老年代的 Region 数量,平衡回收效率与停顿时间。

2. 核心算法:标记 - 复制(局部)+ 标记 - 整理(全局)

G1 结合了两种算法的优点,实现 "无碎片 + 高效率":

  • 局部回收(Minor GC / 混合回收):采用 "复制算法"------ 将存活对象从一个 Region 复制到另一个空 Region,确保回收后的 Region 内存连续;
  • 全局回收(Full GC):采用 "标记 - 整理算法"------ 将所有存活对象向堆的一端移动,清理剩余内存,避免碎片。

3. 垃圾优先策略:基于 "回收收益" 的优先级排序

G1 的 "垃圾优先" 并非随机回收,而是基于回收收益(回收的垃圾量 / 回收耗时) 排序:

  1. 每个 Region 都有一个 "垃圾占比" 统计(即该 Region 中垃圾对象的比例);
  2. G1 维护一个 "优先级列表",按垃圾占比从高到低排序;
  3. 每次 GC 时,G1 根据预设的最大停顿时间(-XX:MaxGCPauseMillis),选择优先级最高的一批 Region 进行回收,确保在规定时间内完成回收。

4. 关键技术:Remembered Set(记忆集)

由于 Region 是独立的内存块,对象可能跨 Region 引用(如 Old Region 的对象引用 Eden Region 的对象)。为了避免 GC 时全堆扫描,G1 引入了Remembered Set(RS) 技术:

  • 每个 Region 都对应一个 Remembered Set,记录其他 Region 对该 Region 中对象的引用;
  • 当 GC 回收某个 Region 时,只需扫描其 Remembered Set,即可找到所有外部引用,无需遍历整个堆;
  • 维护 RS 的开销由 "写屏障" 承担 ------ 当对象引用发生变化时,通过写屏障自动更新对应的 RS。

三、G1 完整工作流程:5 个阶段 + 3 种 GC 模式

G1 的垃圾回收过程分为新生代 GC(Minor GC)、混合 GC(Mixed GC)、Full GC三种模式,核心流程包括 5 个关键阶段,其中大部分阶段支持并发执行,仅关键步骤触发短暂 STW。

1. 三种 GC 模式(核心区别)

GC 模式 清理范围 触发条件 核心特点
新生代 GC(Minor GC) 仅新生代 Region(E+S) 新生代 Eden Region 满 采用复制算法,STW 时间短,频率高
混合 GC(Mixed GC) 新生代 Region + 部分老年代 Region 老年代使用率达到阈值(默认 45%) 核心模式,并发标记 + 并行回收,停顿可控
Full GC 整堆所有 Region(E+S+O+H) 混合 GC 后内存仍不足,或大对象无法分配 基于标记 - 整理算法,STW 时间长,需避免

2. 混合 GC 完整流程(核心重点)

混合 GC 是 G1 的核心工作模式,也是 "低停顿 + 高收益" 的关键,分为 5 个阶段,其中仅初始标记、最终标记、筛选回收需要 STW:

阶段 1:初始标记(Initial Mark)------ STW(短暂)
  • 核心工作:标记 GC Roots 直接关联的对象(如虚拟机栈引用、静态变量引用),同时修改 TAMS(Next Top Mark Start)指针,确保后续用户线程并发运行时,能在空闲 Region 中创建新对象。
  • 通俗理解:回收团队先快速标记 "小区门口直接可见的垃圾",同时划定 "新住户可入住的空小区",避免回收期间影响新对象分配。
  • 特点:STW 时间极短(通常 10ms 内),多线程并行执行。
阶段 2:并发标记(Concurrent Marking)------ 并发(无 STW)
  • 核心工作:从初始标记的对象出发,遍历整个堆的引用链,标记所有存活对象;同时统计每个 Region 的垃圾占比(回收收益),为后续筛选回收做准备。
  • 通俗理解:回收团队与居民(用户线程)并行工作,逐一排查每个小区的存活对象,同时记录每个小区的垃圾量,排序优先级。
  • 特点:与用户线程并发执行,耗时较长(数百 ms),但不影响应用响应;期间用户线程可继续创建对象,产生的新垃圾会被标记为 "浮动垃圾",留到下次 GC 清理。
阶段 3:最终标记(Final Mark)------ STW(短暂)
  • 核心工作:修正并发标记期间因用户线程修改引用导致的标记偏差,将 Remembered Set Logs 中的数据合并到 Remembered Set 中,确保所有存活对象都被正确标记。
  • 通俗理解:回收团队短暂清场,核对之前的标记结果,补充标记并发期间遗漏的存活对象,确保统计的垃圾量准确。
  • 特点:STW 时间比初始标记稍长(通常几十 ms),支持多线程并行执行,效率高。
阶段 4:筛选回收(Live Data Counting and Evacuation)------ STW(可控)
  • 核心工作
    1. 筛选:根据每个 Region 的垃圾占比(回收收益)和预设的最大停顿时间,筛选出一批优先级最高的 Region(新生代 + 部分老年代);
    2. 回收:采用 "复制算法",将筛选出的 Region 中的存活对象并行复制到空 Region 中;
    3. 清理:清空原 Region,标记为空闲 Region,供后续对象分配使用。
  • 通俗理解:回收团队根据 "垃圾多、清理快" 的原则挑选小区,在规定时间内完成清理,将存活的居民转移到空小区,原小区变为空房。
  • 特点 :STW 时间可控(不超过-XX:MaxGCPauseMillis),并行执行回收,回收后内存连续无碎片。
阶段 5:并发清理(Concurrent Cleanup)------ 并发(无 STW)
  • 核心工作:清理筛选回收阶段产生的空 Region,更新 Region 状态,为下一次 GC 做准备。
  • 特点:与用户线程并发执行,无 STW,不影响应用运行。

3. 新生代 GC(Minor GC)流程

当新生代 Eden Region 满时,触发 Minor GC,仅清理新生代的 Eden 和 Survivor Region:

  1. 标记新生代中的存活对象;
  2. 将存活对象复制到空的 Survivor Region(或直接晋升到老年代 Region,若年龄达到阈值);
  3. 清空原 Eden 和 Survivor Region,STW 时间极短(通常几十 ms)。

4. Full GC 流程(需避免)

当混合 GC 后老年代仍无足够内存,或大对象无法分配连续 Region 时,触发 Full GC:

  1. 采用 "标记 - 整理" 算法,暂停所有用户线程(长时间 STW);
  2. 遍历整堆所有 Region,标记存活对象;
  3. 将所有存活对象向堆的一端移动,清理剩余内存;
  4. 特点:STW 时间长(秒级),严重影响应用响应,需通过调优避免。

四、G1 实战调优:参数配置与问题解决

1. 核心 JVM 参数(启用与基础配置)

(1)启用 G1 收集器(JDK9 + 默认,JDK8 需手动启用)
复制代码
-XX:+UseG1GC  # 启用G1收集器(JDK8必须添加,JDK9+默认启用)
(2)内存与分区配置
复制代码
-Xms8g -Xmx8g              # 堆内存初始/最大=8G(建议设为相同,避免动态调整)
-XX:G1HeapRegionSize=4m    # 每个Region大小=4MB(可选,默认JVM自动计算,范围1M~32M)
-XX:G1NewSizePercent=5     # 新生代初始占堆比例(默认5%)
-XX:G1MaxNewSizePercent=60 # 新生代最大占堆比例(默认60%)
(3)核心调优参数(重点)
参数 作用 推荐配置
-XX:MaxGCPauseMillis 指定 GC 最大停顿时间(默认 200ms) 100~200ms(根据业务响应时间要求调整)
-XX:G1ReservePercent 预留内存比例,用于应对晋升失败(默认 10%) 10%~15%(避免混合 GC 时晋升失败触发 Full GC)
-XX:InitiatingHeapOccupancyPercent 老年代使用率阈值,触发混合 GC(默认 45%) 40%~50%(提前触发,避免内存不足)
-XX:ParallelGCThreads GC 并行线程数(默认 = CPU 核心数) 等于 CPU 核心数(充分利用多核资源)
-XX:ConcGCThreads 并发 GC 线程数(默认 = ParallelGCThreads/4) CPU 核心数 / 4(平衡并发 GC 与用户线程 CPU 占用)
-XX:G1HeapWastePercent 允许的堆内存浪费比例(默认 5%) 5%~10%(控制回收精度与停顿时间的平衡)

2. 常见问题与解决方案

(1)混合 GC 停顿时间超标

现象 :GC 日志中混合 GC 的 STW 时间超过-XX:MaxGCPauseMillis设置的阈值(如 200ms),影响应用响应。

原因

  • 单次回收的 Region 数量过多,导致清理时间过长;
  • 新生代占比过大(超过 60%),Minor GC 清理耗时增加;
  • 并发标记阶段耗时过长,导致最终标记和筛选回收阶段压力增大。

解决方案

  • 降低-XX:MaxGCPauseMillis(如从 200ms 调至 150ms),G1 会自动减少单次回收的 Region 数量;
  • 限制新生代最大占比(-XX:G1MaxNewSizePercent=50),避免新生代过大;
  • 增加并发 GC 线程数(-XX:ConcGCThreads=4,针对 8 核 CPU),提升并发标记效率;
  • 提前触发混合 GC(降低-XX:InitiatingHeapOccupancyPercent=40),减少单次回收的垃圾量。
(2)频繁触发 Full GC

现象:应用运行中频繁触发 Full GC,STW 时间长,系统卡顿。

原因

  • 老年代使用率阈值设置过高(如 50% 以上),混合 GC 启动过晚,内存不足;
  • 预留内存比例不足(-XX:G1ReservePercent过小),新生代对象晋升时老年代无足够空间;
  • 大对象过多,超过 Region 大小的一半,直接分配到 Humongous Region,快速占满老年代。

解决方案

  • 降低-XX:InitiatingHeapOccupancyPercent=40,让混合 GC 提前触发,避免老年代满;
  • 增大预留内存比例(-XX:G1ReservePercent=15),为晋升对象预留足够空间;
  • 调整-XX:G1HeapRegionSize,让大对象能适配 Region 大小(如大对象为 8MB,Region 设为 8MB);
  • 优化应用代码,减少大对象创建(如拆分大集合、分页查询大数据量)。
(3)新生代 GC 频率过高

现象:Minor GC 每秒触发多次,虽然单次停顿短,但累计停顿时间影响吞吐量。

原因

  • 新生代初始占比过小(-XX:G1NewSizePercent=5),Eden Region 快速被占满;
  • 应用频繁创建短期存活的对象,导致 Eden Region 频繁溢出。

解决方案

  • 增大新生代初始占比(-XX:G1NewSizePercent=10),减少 Minor GC 触发频率;
  • 优化应用代码,减少临时对象创建(如复用 StringBuilder、使用对象池);
  • 调整-XX:MaxTenuringThreshold(对象晋升老年代年龄阈值,默认 15),让短期存活对象在新生代多回收几次,减少晋升压力。

3. G1 日志分析示例

启用 G1 后,通过以下参数打印 GC 日志(简化版):

复制代码
-Xloggc:/tmp/gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime

关键日志解读(混合 GC 示例):

复制代码
# 初始标记(STW)
[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0080000 secs]
   [Parallel Time: 6.0 ms, GC Workers: 8]
   [GC Roots Scanning: 1.0 ms]
   [Marking: 3.0 ms]
   [Concurrent Mark Start]

# 并发标记
[GC concurrent-mark-start]
[GC concurrent-mark: 120.0 ms] [Times: user=300.0 ms, sys=20.0 ms, real=120.0 ms]

# 最终标记(STW)
[GC pause (G1 Humongous Allocation) (young) (final-mark), 0.0200000 secs]
   [Parallel Time: 15.0 ms, GC Workers: 8]
   [Root Region Scan: 5.0 ms]
   [Marking: 8.0 ms]

# 筛选回收(STW)
[GC pause (G1 Humongous Allocation) (mixed), 0.0500000 secs]
   [Parallel Time: 45.0 ms, GC Workers: 8]
   [Evacuation Time: 40.0 ms]  # 复制存活对象时间
   [Region Cleanup: 3.0 ms]
   [Heap Usage: Before GC: 6144M/8192M, After GC: 3072M/8192M]  # 堆使用情况

# 并发清理
[GC concurrent-cleanup-start]
[GC concurrent-cleanup: 5.0 ms] [Times: user=10.0 ms, sys=1.0 ms, real=5.0 ms]

关键解读:

  • 初始标记 STW:0.008 秒,最终标记 STW:0.02 秒,筛选回收 STW:0.05 秒,总 STW 时间 0.078 秒,符合-XX:MaxGCPauseMillis=100ms的要求;
  • 并发标记耗时 120 秒(无 STW),堆内存从 6GB 降至 3GB,回收效果显著;
  • 无内存碎片相关日志,说明回收后内存连续。

五、G1 与其他收集器的对比:如何选择?

收集器组合 核心优势 适用场景 缺点
G1 低停顿、无碎片、可预测、大内存高效 大内存、响应时间优先的服务(JDK9 + 首选) 配置稍复杂,小内存(<4G)场景优势不明显
ParNew+CMS 低停顿、配置简单 JDK8 及以前的响应时间优先服务 内存碎片、并发模式失败风险高
Parallel Scavenge+Parallel Old 高吞吐量、配置简单 计算密集型、吞吐量优先的服务 停顿时间不可控,不适合 Web 应用
ZGC/Shenandoah 超低停顿(毫秒级)、超大内存支持 超大内存(≥64G)、极致低延迟场景 JDK11 + 才支持,部分功能仍在优化

选择建议

  • JDK9+:优先使用 G1,无需额外配置,默认即可满足大部分场景;
  • JDK8:若需低停顿,可选择 ParNew+CMS;若追求稳定性和无碎片,可启用 G1(需手动添加-XX:+UseG1GC);
  • 超大内存(≥64G)+ 极致低延迟(<10ms):JDK11 + 可选择 ZGC/Shenandoah;
  • 计算密集型、无响应时间要求:选择 Parallel Scavenge+Parallel Old,追求最大吞吐量。

六、G1 调优核心原则与最佳实践

1. 调优核心原则

  • 停顿优先,兼顾吞吐量 :G1 的核心优势是低停顿,调优时优先保证-XX:MaxGCPauseMillis达标,再通过调整并发线程数、回收阈值等优化吞吐量;
  • 避免 Full GC:Full GC 是 G1 的 "性能杀手",调优的核心目标之一是通过合理配置,让混合 GC 能及时回收老年代,避免触发 Full GC;
  • 适配硬件资源 :GC 线程数(ParallelGCThreadsConcGCThreads)应根据 CPU 核心数调整,避免 GC 线程与用户线程争夺 CPU 资源;
  • 不盲目调参:G1 的默认参数已适配大部分场景,非必要不修改,仅在出现性能问题时针对性调优。

2. 最佳实践

  • 堆内存配置-Xms-Xmx设为相同值,避免 GC 时动态扩展内存的开销;堆内存建议≥4G,充分发挥 G1 的分区优势;
  • Region 大小选择 :默认由 JVM 自动计算(堆大小 / 2048),若存在大量大对象,可手动调整-XX:G1HeapRegionSize,让大对象能适配 Region 大小;
  • 最大停顿时间设置:根据业务要求设置(如 Web 应用设为 100~200ms),不宜过小(如 < 50ms),否则会导致回收不充分,反而增加 GC 频率;
  • 监控重点指标 :通过 Prometheus+Grafana、JVisualVM 等工具监控:
    • GC 停顿时间(是否达标MaxGCPauseMillis);
    • 混合 GC 触发频率(是否过于频繁);
    • 老年代使用率(是否长期接近InitiatingHeapOccupancyPercent);
    • Full GC 触发次数(应尽量为 0)。
相关推荐
左左右右左右摇晃2 小时前
JVM 整理(三) 方法区+虚拟机栈
jvm·笔记
森林里的程序猿猿2 小时前
垃圾收集器ParNew&CMS与底层标记三色标记算法
java·jvm·算法
t_hj2 小时前
腾讯QClaw深度试用:一句话创建专业级网络爬虫
开发语言·python
老毛肚2 小时前
八股框架篇
java·开发语言
大黄说说2 小时前
Rust 入门到实战:构建安全、高性能的下一代系统
开发语言·安全·rust
毅炼2 小时前
Spring 总结(1)
java·开发语言·spring
jing-ya2 小时前
day 55 图论part7
java·数据结构·算法·图论
阿蒙Amon2 小时前
C#常用类库-详解NModbus4
开发语言·c#