一、 为什么需要 G1?(化整为零的思想)
在 G1 出现之前,无论是新生代(Minor GC)还是老年代(CMS),垃圾回收总是面对一整块“巨大的内存”。这导致:
STW 时间长:扫描全堆(或全代)非常耗时。
内存碎片:CMS 使用“标记-清除”算法,回收后产生大量不连续空间,导致大对象无法分配,触发 Full GC。
G1 的核心突破:区域化(Region)。
G1 将整个堆内存(Heap)化整为零,划分成了大小相同的多个独立子区域(Region)。这就好比将一整块大的田地,划分成了一块块小方格,G1 可以针对性地回收某些“脏”的方格,而不必每次都犁遍整块地。
Region 的大小:JVM 启动时自动设定,通常为
1MB ~ 32MB(且必须是 2 的幂)。默认会将整堆划分为2048个 Region。内存上限:通过参数
-XX:G1HeapRegionSize=n可指定大小。由于最大只有 2048 个区域,所以最大支持32MB * 2048 = 64GB的堆内存。
✨ G1 的五大“杀手锏”特性:
并行性:回收期间多线程并行工作,充分利用多核 CPU 缩短 STW。
并发性:部分回收步骤与应用程序(用户线程)交替执行,不阻塞应用。
分代 GC(逻辑上):虽然物理上不再区分新生代和老年代,但逻辑上依然存在 Eden、Survivor 和 Old 区,只是这些区域是可以动态切换的。
空间整理:通过复制算法回收,每次 GC 都会压缩空间,彻底解决内存碎片问题。
可预见性:由于分区,G1 可以预测停顿时间,只选择部分垃圾最多的 Region 进行回收(即
CSet),极大降低了单次 GC 的影响。
二、 图解 G1 堆内存布局(Region 地图)
G1 的内存布局不再像传统堆那样是固定的Eden和Old长条,而是由无数个Region方格组成:
🟩 E (Eden):新生代,存活率低,GC 频繁。
🟧 S (Survivor):幸存者区,用于存活对象在新生代之间的周转。
🟦 O (Old):老年代,存放长生命周期对象。
🟥 H (Humongous):巨型对象区(G1 独有的特殊区域)。当一个对象的大小超过单个 Region 的 50%,G1 会将其分配在连续的 H 区。
坑点提示:如果 H 区装不下,需要连续拼接多个 H 区。如果找不到连续空间,就会导致 Full GC。
👉 核心逻辑:这些 Region 并不是固定不变的。在运行过程中,一个原本是Eden的区域可能经过回收后变为Old,完全取决于需求。这种“逻辑分代、物理分区”的设计,也是 G1 灵活性的来源。
三、 G1 的两大回收过程详解
G1 的回收分为两种核心场景:新生代 GC (Young GC)和并发标记混合 GC (Mixed GC)。
1. 新生代 GC (Young GC) —— “复制与晋升”
触发时机:当 Eden 区的空间耗尽时,触发 Young GC。
操作流程(基于复制算法):
Eden ➡️ Survivor:将 Eden 区中存活的对象复制到一个 Survivor 区。
Survivor ➡️ Survivor/Old:将原 Survivor 中的存活对象,根据年龄(Age)判定,移到另一个 Survivor 区或直接晋升到 Old 区(如果年龄达标或 Survivor 容量不足)。
清空 Eden:垃圾对象被彻底清理,形成连续的内存块。
特点:这是一个完全 STW(Stop-The-World)的过程,但只扫描 Eden 区和相关的 Survivor 区,速度极快。
2. 并发标记周期与混合 GC (Mixed GC) —— “渐进式清理”
目标:处理老年代(Old 区)和巨型区(H 区)的垃圾,降低整体停顿。
整个并发标记周期分为 6 个精准步骤(重点!):
| 步骤 | 名称 | 是否 STW | 核心动作详解 |
|---|---|---|---|
| ① | 初始标记 | ✅STW | 标记从GC Roots直接可达的对象。注意:这一步会伴随一次新生代 GC 执行,标记过程极短。 |
| ② | 根区域扫描 | ❌ 并发 | 扫描 Survivor 区直接可达的老年代对象。(不能和新生代 GC 同时执行,因为新生代会修改 Survivor 区) |
| ③ | 并发标记 | ❌ 并发 | 最长、最耗时的阶段。GC 线程与应用线程共同运行,扫描整个堆,查找存活对象。应用线程的变动会通过SATB (Snapshot-At-The-Beginning)算法记录快照,防止误判。 |
| ④ | 重新标记 | ✅STW | 由于并发标记期间应用还在跑,结果可能不准确。这一步会补充修正标记,最终确定存活对象。 |
| ⑤ | 独占清理 | ✅STW | 统计各个 Region 的存活对象比例,进行排序,并识别出垃圾最多(Garbage-First)的区域,确定下一次混合回收的CSet(集合)。 |
| ⑥ | 并发清理 | ❌ 并发 | 仅清除完全空闲的 Region。 |
👉 混合回收 (Mixed GC):在并发标记完成后,G1 会挑选出之前识别的“高收益” Region(即垃圾最多的那几个),执行一次混合回收。这个过程也是复制算法,会搬运存活对象到新的 Region,从而腾出大量空间并顺便完成内存碎片整理。
四、 不得已的 Full GC —— 鱼和熊掌的平衡
尽管 G1 很强大,但世界并非完美。
Full GC 触发:当内存分配太快,并发收集跟不上的时候(例如并发标记还没结束,老年代就满了),G1 不得不降级,退化为单线程的 Full GC(即完全 STW,且速度很慢,性能骤降)。
与 CMS 的相似性:和 CMS 类似,G1 依然无法 100% 避免全堆停顿,特别是在高并发、高频分配的大内存场景下。
五、 G1 实战调优参数(核心配置清单)
了解底层原理,是为了更好的调优。以下是 G1 最关键的几个参数:
| 参数名 | 默认值 | 作用及调优建议 |
|---|---|---|
-XX:+UseG1GC | 无 | 开启 G1 垃圾收集器。从 JDK 9 起,G1 已经是默认垃圾回收器。 |
-XX:MaxGCPauseMillis | 200ms | 停顿时间目标。G1 会为了达到这个目标,动态调整年轻代大小(Eden/Survivor)。注意:设置过小(如 50ms),会导致 GC 更频繁,吞吐量下降;设置过大,可能会使 Full GC 风险增加。 |
-XX:ParallelGCThreads | CPU 核心数 | 设置并行回收时的GC 工作线程数。通常不建议修改,让 JVM 自动探测即可。 |
-XX:InitiatingHeapOccupancyPercent | 45 | 触发并发标记的堆占用阈值。当整个堆占用率达到 45% 时,开始并发标记周期。 ⚠️重要警醒:此参数设置过大(如 >50%),可能导致堆满而触发 Full GC;设置过小(如 <30%),会导致并发标记频繁启动,大量占用 CPU,拖累业务应用性能。 |
调优黄金法则:G1 的目标是“软实时”,即MaxGCPauseMillis是一个尽力而为的目标,而不是强制标准。在调优时,我们应该优先关注吞吐量和Full GC 的频率,不要盲目追求极端的低停顿。
六、 附:G1 典型 GC 日志解析
看懂了日志,你才能知道 JVM 在底层干了什么。这是一段典型的 G1 Young GC 日志:
[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0080719 secs] [Parallel Time: 5.1 ms, GC Workers: 10] <-- 并发工作线程数量,STW耗时 [Eden: 6144.0K(6144.0K)->0.0B(1024.0K) <-- Eden区 6MB -> 0MB,新Eden为1MB Survivors: 0.0B->0.0B <-- Survivor 空间变化 Heap: 8262.9K(10.0M)->6516.9K(10.0M)] <-- 整个堆从 8.2MB 降低到 6.5MB [Times: user=0.00 sys=0.00, real=0.01 secs]日志解读:
GC pause (young) (initial-mark):这是一次 Young GC,并且伴随了“初始标记”。Heap: ... -> ...:说明堆内存发生了回收,垃圾被清理了。时间开销:
real=0.01 secs(10ms),说明这次 GC 停顿几乎对用户无感知。
并发标记结束日志:
[GC concurrent-root-region-scan-start] <-- 开始根区域扫描 (并发) [GC concurrent-root-region-scan-end, 0.0000227s] [GC concurrent-mark-start] <-- 开始并发标记 (并发) [GC concurrent-mark-end, 0.0008297s] [GC remark ... 0.0017108 secs] <-- 重新标记 (STW) [GC cleanup 7215K->7215K(10M), 0.0012619 secs] <-- 清理空闲区域 (并发)总结
G1 作为一款现代化的垃圾回收器,其核心价值在于“化整为零”的 Region 设计和“暂停时间可控”的混合回收机制。它不仅解决了 CMS 长期存在的内存碎片问题,还通过并发与并行极大地降低了 STW 的冲击。
但也要记住:
一切优化都有代价。为了降低停顿,G1 牺牲了部分内存空间(如额外的记忆集 Remembered Set)和 CPU 计算资源。
在 64GB 以下的大内存、对响应时间(RT)敏感、多核 CPU 的服务器环境下,G1 是现阶段的最佳选择。