🎯 引言:G1 的“分而治之”哲学
G1 (Garbage-First) 收集器是现代 Java 虚拟机(JVM)默认的垃圾收集器,它的设计彻底颠覆了传统的“分代收集”理念,引入了Region(区域)的概念,实现了可预测的停顿时间。
本篇博客将带你深入 G1 的底层架构,理解它是如何通过精妙的区域划分、智能的大对象处理和优化的 GC 流程,实现高吞吐量与低延迟的完美平衡。
一、🌍 堆内存的基石:Region 划分与动态结构
G1 GC 的一切优势都建立在它的堆内存结构上:将整个 Java 堆划分为多个大小相等的独立区域(Region) 。
1. Region 的基本设定
| 特征 | 细节说明 |
|---|---|
| 数量上限 | JVM 最多可以拥有 2048 个 Region。 |
| 默认大小 | Region 大小通常等于 堆总大小 / 2048。例如,堆大小为 4096M,则每个 Region 大小约为 2M。 |
| 手动指定 | 可以通过参数 -XX:G1HeapRegionSize 手动指定大小,但推荐 JVM 默认计算。 |
2. 动态灵活的分代概念
G1 虽然保留了年轻代和老年代的概念,但它们不再是物理连续的内存块。
- 分代构成: 年轻代和老年代都是由 一组 Region 的集合 构成的,这些 Region 在堆内存中可以不连续。
- 功能动态变化: 一个 Region 可能在 Young GC 后被清空,随后被重新分配给老年代使用,这意味着 Region 的区域功能(年轻代/老年代)会动态变化。
3. 年轻代的动态调整
G1 的新生代大小是动态调整的,以更好地满足停顿时间目标:
- 初始占比: 默认情况下,年轻代占堆内存的 5% (可通过
-XX:G1NewSizePercent设置初始值)。 - 最大限制: 无论如何增加,新生代的占比不会超过 60% (可通过
-XX:G1MaxNewSizePercent调整)。 - Eden/Survivor 比例: 新生代内部的 Eden 区和 Survivor 区(S0、S1)仍然遵循经典的 8:1:1 比例进行 Region 划分。
二、🐘 大对象的专门通道:Humongous 区
G1 对待大对象的方式非常特殊,它专门引入了 Humongous 区,目的是减轻老年代的负担。
1. 大对象的判定规则
-
一个对象的大小超过了一个 Region 大小的 50% ,即被判定为大对象。
- 例如:如果 Region 大小是 2M,那么超过 1M 的对象就是大对象。
2. Humongous 区的作用
-
分配位置: 大对象会被直接分配到 Humongous Region 中,而不会直接进入普通的老年代 Region。
-
优势:
- Humongous 区专门用于存放短期巨型对象。
- 它节约了老年代的正常空间,避免老年代过快饱和而引发不必要的 GC 开销。
- 如果大对象太大,它可能会横跨多个 Region 来存放。
3. 回收机制
- 在正常的 Young GC 和 Mixed GC 中,Humongous 区的对象不会被回收。
- 只有在 Full GC 发生时,Humongous 区的对象才会与年轻代和老年代一起被一并回收。
三、🔄 G1 垃圾收集的运作流程(四阶段)
G1 的垃圾收集运作流程,尤其是它的 并发标记 阶段,与 CMS 收集器有相似之处,但其最后的 筛选回收 阶段是独一无二的。
| 阶段 | 停顿类型 | 核心任务 | 特点与深度解析 |
|---|---|---|---|
| 1. 初始标记 | STW (Pause) | 记录 GC Roots 直接引用的对象。 | 速度极快,因为它只标记与 GC Roots 直接关联的对象。 |
| 2. 并发标记 | 并发 (Concurrent) | 从 GC Roots 开始,遍历整个堆,标记所有存活对象。 | 与应用程序线程同时运行,不造成停顿。此阶段会统计每个 Region 的存活对象比例。 |
| 3. 最终标记 | STW (Remark) | 处理并发标记期间产生的新的引用变动(SATB)。 | 再次暂停应用线程,确保所有存活对象被标记,完成对象图的精确化。 |
| 4. 筛选回收 | STW (Pause) | 根据回收价值和成本,制定回收计划并执行。 | 这是 G1 “可预测停顿” 的核心实现: |
| 1. 价值排序: 对所有 Region(包括年轻代和垃圾占比高的老年代)的回收价值和预计成本进行排序。 | |||
2. 制定计划: 根据用户设定的最大停顿时间 (-XX:MaxGCPauseMillis),选择刚好在预算内能回收掉 Region 集合(Collection Set, CSet)。 | |||
| 3. 执行回收: 对 CSet 中的 Region 使用复制算法,将存活对象复制到新的空闲 Region。 |
💡 关键区别: G1 采用复制算法进行回收(即使是老年代),这使其在回收后几乎不会产生内存碎片,从而避免了 CMS 收集器回收后的“整理”步骤。
🔑 总结:G1 机制的核心优势
G1 收集器的成功,在于它将 可预测的低延迟 深度融入其底层设计:
- 分治管理: 通过 Region 划分,将大堆内存的回收压力分散到可控的小区域。
- 停顿预算: 筛选回收 阶段严格根据
-XX:MaxGCPauseMillis来增量地选择要回收的 Region,确保单次停顿时间不会失控。 - 无碎片化: 采用 复制算法 进行回收,天然具备整理内存的能力,保持堆内存的整洁。
理解 G1 的 Region 结构和筛选回收机制,是进行高并发 Java 应用 GC 调优的关键!