一、G1 垃圾回收器概述
G1(Garbage-First Garbage Collector)是 Java 7 引入的现代化垃圾回收器,专为大内存、多核 CPU 环境设计,核心目标是控制垃圾回收停顿时间,同时兼顾吞吐量。其设计颠覆了传统分代回收器(如 Parallel GC、CMS)的固定分代模型,采用动态分代 + Region 化内存管理,通过精细化的分代策略和停顿优化算法,实现了可预测的低停顿垃圾回收。
二、G1 的分代设计:从 “固定分代” 到 “动态 Region”
1. 打破固定分代:Region 化内存布局
-
Region 基本概念:
G1 将堆内存划分为多个大小相等(1MB~32MB,由-XX:G1HeapRegionSize
动态确定)的Region,每个 Region 可动态扮演以下角色:- Eden Region:存储新创建的对象(新生代)。
- Survivor Region:存储经历一次 Minor GC 后存活的对象(新生代)。
- Old Region:存储多次 GC 后仍存活的对象(老年代)。
- Humongous Region:存储大小超过单个 Region 50% 的大对象(连续多个 Region 组成)。
-
分代的动态性:
传统分代回收器(如 Serial GC)的新生代、老年代是固定物理区域,而 G1 的分代是逻辑概念:- 新生代由多个不连续的 Eden 和 Survivor Region 组成,占比动态变化(默认不超过堆的 50%)。
- 老年代由晋升的 Old Region 组成,无需连续内存,避免碎片化。
-
Humongous Region 设计:
大对象直接分配到 Humongous Region(连续多个 Region),避免频繁触发 Full GC(传统回收器中大对象易填满老年代),同时通过 Region 化管理降低大对象回收成本。
2. 分代回收的核心逻辑
-
新生代回收(Minor GC):
- 仅回收 Eden 和 Survivor Region(存活对象晋升至 Old 或新 Survivor)。
- 基于复制算法,将存活对象复制到新的 Survivor/Old Region,清空原 Region,效率高且无碎片。
-
混合回收(Mixed GC):
- 当老年代占用超过阈值(
-XX:InitiatingHeapOccupancyPercent
,默认 45%)时触发。 - 回收所有新生代 Region + 部分老年代 Region(根据 “回收收益” 排序,优先处理垃圾占比高的 Old Region),避免全堆扫描。
- 当老年代占用超过阈值(
-
分代与跨代引用:
- 通过 Remembered Sets(RSet) 记录本 Region 对象对其他 Region 对象的引用,避免 GC 时扫描全堆。
- 每个 Region 维护一个 RSet,由 Card Table(全局结构,记录堆中对象所属 Region)辅助更新,大幅减少跨代引用的扫描范围。
三、G1 的停顿优化:如何实现可预测的低延迟
1. 可预测的停顿时间模型
- 用户目标驱动:
通过-XX:MaxGCPauseMillis
设置期望的最大停顿时间(默认 200ms),G1 基于此动态调整每次回收的 Region 数量和类型:- 优先回收垃圾占比高的 Region(“Garbage-First” 名称由来),确保单位时间内回收效率最大化。
- 新生代回收时,根据历史数据估算停顿时间,动态调整新生代大小(Eden/Survivor Region 数量)。
2. 关键技术实现
(1)并发标记与增量回收
-
标记阶段流程:
- 初始标记(STW):标记 GC Roots 直接引用的对象,时间极短(仅扫描新生代)。
- 并发标记:与应用程序并行执行,遍历整个堆,标记可达对象,期间通过 SATB(Snapshot At The Beginning) 算法记录增量引用变化。
- 重新标记(STW):处理并发标记阶段遗漏的对象(基于 SATB 日志),时间短于 CMS 的重新标记。
- 筛选回收(STW):根据停顿目标选择待回收的 Region,执行复制 / 清理算法。
-
并发标记的优势:
避免传统回收器(如 CMS)在标记阶段的长时间 STW,将大部分标记工作与应用程序并行执行,减少总停顿时间。
(2)Region 化细粒度回收
-
局部化回收:
每次 GC 仅处理部分 Region(新生代或部分老年代),而非全堆回收。例如:- 新生代回收时,仅涉及少量 Eden/Survivor Region,停顿时间与 Region 数量正相关(而非堆大小)。
- 混合回收时,通过 “回收收益” 排序(G1 维护每个 Region 的垃圾占比),优先处理垃圾多的 Old Region,在目标时间内完成高价值回收。
-
内存整理:
基于复制算法(新生代)和部分标记 - 整理算法(老年代),回收后 Region 被清空或紧凑,避免内存碎片,减少 Full GC 概率。
(3)Remembered Sets(RSet)的作用
- 跨代引用的本地化:
RSet 记录本 Region 对象对其他 Region 对象的引用,使得 GC 时只需扫描当前 Region 的 RSet,而非全堆。例如:- 当回收某个 Old Region 时,通过 RSet 快速找到所有引用该 Region 对象的外部引用(来自其他 Region),避免全局扫描。
- 减少标记阶段的工作量,尤其在老年代回收时,大幅降低跨代引用的处理成本。
3. 与传统分代回收器的对比
特性 | G1 | 传统分代回收器(如 Parallel GC、CMS) |
---|---|---|
分代方式 | 动态 Region(逻辑分代) | 固定物理分代(新生代 / 老年代) |
内存布局 | 不连续 Region | 连续内存区域(新生代 / 老年代需连续) |
停顿控制 | 可预测(基于 MaxGCPauseMillis) | 不可预测(依赖堆大小和对象分布) |
跨代引用处理 | RSet 本地化扫描 | 全堆扫描或全局 Card Table(效率较低) |
大对象处理 | Humongous Region 分块管理 | 直接进入老年代(易触发 Full GC) |
碎片问题 | 复制算法 + 局部整理,几乎无碎片 | 标记 - 清除可能产生碎片(CMS) |
四、G1 的适用场景与局限性
1. 适用场景
- 大内存应用:堆内存 4GB+,Region 化设计减少全堆操作开销。
- 低延迟敏感场景:如 Web 服务器、微服务(需控制请求响应时间),通过
MaxGCPauseMillis
避免长时间 STW。 - 混合工作负载:既有短生命周期对象(新生代为主),又有长生命周期对象(老年代晋升频繁),G1 的混合回收策略高效平衡回收效率与停顿。
2. 局限性
- 内存占用增加:RSet 和 Card Table 带来额外元数据开销(约占堆的 10%~20%)。
- 并发标记的复杂性:SATB 算法需要额外的内存空间记录对象引用变化,可能增加 Minor GC 耗时。
- Full GC 退化为单线程:若 G1 无法在目标时间内完成回收(如内存碎片严重、大对象分配过快),会退化为单线程 Full GC,性能骤降(需通过参数调优避免)。
五、总结:G1 分代设计与停顿优化的核心价值
G1 的分代设计通过Region 化动态分代,将传统固定分代的 “粗犷管理” 转化为 “细粒度控制”,结合 RSet 本地化引用处理和可预测停顿模型,实现了以下突破:
- 分代的灵活性:不再受限于固定内存区域,适应对象生命周期的动态变化。
- 停顿的可预测性:通过优先回收高价值 Region,确保每次 GC 停顿在用户设定范围内。
- 跨代引用的高效处理:RSet 避免全堆扫描,大幅提升标记阶段效率。
对于现代 Java 应用(尤其是微服务、云原生场景),G1 的低延迟特性使其成为首选回收器之一。理解其分代设计与停顿优化原理,有助于开发者通过参数调优(如 G1HeapRegionSize
、MaxGCPauseMillis
)进一步释放性能潜力,避免 Full GC 退化问题。