JVM 几种常用垃圾收集器的比较
几种常用垃圾收集器(GC),包括 Serial(串行)、Parallel(并行)、CMS(Concurrent Mark-Sweep,并发标记清除) 和 G1(Garbage-First,并发) 收集器。
| 特性 / 收集器 | Serial | Parallel | CMS | G1 |
|---|---|---|---|---|
| 工作模式 | 单线程(串行) | 多线程(并行) | 多线程并发(主要与用户线程并发执行) | 多线程并发 + 分区(Region)并行 |
| Stop-The-World | 较长(整个 GC 过程) | 较长(比 Serial 更快) | 尽量减少暂停时间(有两次短暂停) | 可预测的暂停时间(目标可设,如 <200ms) |
| 吞吐量 | 低 | 高(适合后台批处理) | 中等(以降低吞吐换取低延迟) | 平衡吞吐与延迟 |
| 适用场景 | 单核 CPU、小型应用(如客户端程序) | 多核 CPU、高吞吐需求(如后台计算) | 响应时间敏感、中等堆(自 Java 14已废弃) | 大堆(4GB+)、需要可预测暂停时间(推荐现代应用) |
| 堆结构 | 新生代 + 老年代(连续) | 同左 | 同左 | 分区(Region),逻辑上分新生代/老年代 |
| 是否压缩 | 是(Full GC 时) | 是(Full GC 时) | 不压缩(可能导致碎片,触发 Full GC) | 压缩(混合 GC 期间进行) |
| Java 版本支持 | 所有版本 | 所有版本 | Java 5–13(Java 14+ 移除) | Java 7u4+ 默认(Java 9+ 成为默认 GC) |
-
Serial GC:最简单,适用于单 CPU 或内存受限环境(如嵌入式、小型应用)。
-
Parallel GC(也称吞吐量收集器):注重吞吐量(即应用运行时间 / 总时间),适合不需要低延迟的后台任务。
-
CMS GC:目标是减少停顿时间,适合 Web 服务器等需要快速响应的场景,但因复杂性和碎片问题,已被弃用。
-
G1 GC:设计用于替代 CMS,支持大堆内存,通过分区和并发标记实现可预测的停顿时间,是现代 Java 应用(尤其微服务)的首选。
Java 8:若需低延迟,可尝试 G1(需手动开启)。
Java 9+:G1 是默认 GC,通常无需更改。
超大堆(>16GB)或极低延迟需求:可考虑 ZGC(Java 11+ 实验,Java 15+ 生产就绪)或 Shenandoah(OpenJDK 12+)。
Serial 收集器(串行)
-
单线程执行所有 GC 操作(包括新生代 Minor GC 和老年代 Full GC)。
-
使用 复制算法(Copying) 回收新生代。
-
使用 标记-整理(Mark-Compact) 算法回收老年代(避免内存碎片)。
-
GC 期间 Stop-The-World(STW),即整个应用线程暂停。
最简单的 GC,代码精简,开销低。
适用于 单核 CPU 或 资源受限环境(如嵌入式系统、小型桌面应用)。
内存占用最小(无额外线程或复杂数据结构)。
使用建议
默认用于客户端模式(-client) 的 JVM(Java 8 及更早)。
通常在 堆内存 ≤ 几百 MB 的场景下表现良好。
不适合服务器端高并发或大堆应用。
1 -XX:+UseSerialGC # 启用
优化建议
控制堆大小:避免堆过大(如
-Xmx256m),否则 STW 时间过长。减少对象分配速率:降低 Minor GC 频率。
监控 GC 日志:
-Xloggc:gc.log -XX:+PrintGCDetails
Parallel 收集器(并行)
-
多线程并行执行 GC(线程数默认 = CPU 核数)。
-
新生代使用 并行复制算法,老年代使用 并行标记-整理。
-
仍为 Stop-The-World,但利用多核加速 GC 过程。
-
目标:最大化吞吐量(即应用运行时间占比)。
高吞吐量:适合批处理、科学计算、后台任务等对延迟不敏感的场景。
自 Java 8 起是 服务器模式(-server)的默认 GC。
相比 Serial,GC 时间更短(多线程并行),但暂停时间仍可能较长。
使用建议
适用于 多核服务器、大堆(几 GB)、后台任务。
不适用于 Web 服务等需要低延迟的场景。
1 -XX:+UseParallelGC # 同时启用新生代 Parallel Scavenge + 老年代 Parallel Old
优化建议
调整堆大小
控制 GC 线程数
设置吞吐量目标(JVM 自动调整堆和 GC 行为)
避免频繁 Full GC:监控老年代增长,优化对象生命周期。
1
2
3
4
5
6
7 -Xms4g -Xmx4g # 避免动态扩容开销
-XX:ParallelGCThreads=8 # 通常设为 CPU 核数
-XX:GCTimeRatio=19 # 目标:95% 时间用于应用(1/(1+19) = 5% GC)
-XX:MaxGCPauseMillis=200 # JVM 会尽量满足,但**不保证**(仅作参考)
CMS 收集器(并发)
-
以 最小化停顿时间 为目标。
-
老年代使用 并发标记-清除(Mark-Sweep),与应用线程并发执行。
-
GC 阶段:
- Initial Mark(STW,快)
- Concurrent Mark(并发)
- Remark(STW,较慢)
- Concurrent Sweep(并发)
-
新生代仍使用 ParNew(并行复制)。
低停顿时间:适合 Web 服务器、API 服务等延迟敏感场景。
不压缩内存 :可能产生内存碎片 , 触发 Full GC(Serial Old)。
CPU 资源消耗高:并发阶段占用 CPU。
浮动垃圾(Floating Garbage):并发期间新死亡对象无法回收,需预留空间。
使用和优化
使用建议(仅限 Java ≤13)
中等堆(2–6 GB)、延迟敏感、能接受略低吞吐量。
不适用于大堆(碎片问题严重)或 高分配速率 场景。
1 -XX:+UseConcMarkSweepGC # 启用
优化建议
预留老年代空间(防并发模式失败)
增加 GC 线程
避免 Full GC:
- 监控 CMS 失败(“Concurrent mode failure”), 增大堆或降低 CMS 触发阈值。
- 减少大对象分配(直接进入老年代)。
碎片整理(谨慎使用)
1
2
3
4
5
6
7 -XX:CMSInitiatingOccupancyFraction=70 # 老年代占用 70% 时启动 CMS
-XX:+UseCMSInitiatingOccupancyOnly # 禁止 JVM 动态调整
-XX:ConcGCThreads=2 # 并发阶段线程数(通常为 ParallelGCThreads 的 1/4)
-XX:+UseCMSCompactAtFullCollection # Full GC 时压缩(已废弃)
-XX:CMSFullGCsBeforeCompaction=5 # 每 5 次 Full GC 压缩一次
并发标记流程
在应用程序运行的同时识别垃圾对象。其核心目标是 减少 Stop-The-World(STW)暂停时间,提升应用响应性。
标记过程(基于增量更新):
-
Initial Mark(STW)
- 暂停所有应用线程。
- 从 GC Roots(栈、寄存器、静态变量等)出发,标记直接可达对象为灰色。
- 非常快(仅扫描根集合)。
-
Concurrent Mark(并发)
- 应用线程恢复运行。
- GC 线程从灰色对象出发,深度优先遍历引用图,将灰色变黑,白色变灰。
- 期间应用线程可修改引用。
-
Remark(STW)
- 再次暂停应用线程。
- 处理并发期间被写屏障记录的引用变更(见下文)。
- 完成最终标记,所有存活对象变为黑色。
-
Concurrent Sweep(并发)
- 并发回收白色对象(不移动对象,故不压缩)。
写屏障策略:Incremental Update(增量更新)
问题场景:若在并发标记期间,应用线程执行
A.ref = B,且 A 已是黑色,B 是白色, 违反不变性!CMS 的应对:写屏障记录 “新生成的从黑色到白色的引用”(即 A 到 B)。在 Remark 阶段,将 A 重新标灰,使其在 STW 中被重新扫描,从而发现 B。
存在问题:Remark 阶段可能很长(需重新扫描大量对象),且不压缩内存,易碎片化。
G1 收集器(并发)
-
面向 大堆(4GB+) 和 可预测停顿时间。
-
将堆划分为 多个等大小 Region(默认 2048 个),每个 Region 可扮演 Eden、Survivor 或 Old 角色。
-
使用 并发标记(SATB 算法) + 增量整理(Evacuation)。
-
GC 类型:
- Young GC:回收年轻代 Region(STW,并行)。
- Mixed GC:在 Young GC 基础上,同时回收部分老年代 Region(基于“回收价值”排序)。
-
目标:在用户设定的 MaxGCPauseMillis 内完成 GC。
可预测停顿时间(如 50–200ms)。
自动平衡吞吐与延迟。
压缩内存 → 无碎片问题。
适用于 大堆、多核、混合负载(既有短生命周期对象,也有长生命周期对象)。
使用和优化
Java 9+ 默认 GC。
推荐用于 微服务、Web 应用、大内存应用(4GB–几十 GB)。
不适合堆 < 4GB(Region 开销占比过高)。
1 -XX:+UseG1GC # 启用
优化建议
设定最大暂停时间目标(JVM 会调整 GC 频率和回收集大小)
调整 Region 大小(通常无需手动设置)
控制 Mixed GC 行为
避免大对象(Humongous Object)
- 50% Region 大小的对象会分配到 Humongous Region,导致回收效率低。
- 优化方案:减少大数组/缓存,或增大 Region(如
-XX:G1HeapRegionSize=32m)。监控关键指标:
- Young GC 时间
- Mixed GC 频率
- Remembered Sets / Card Table 开销
- Humongous Allocation 比例
1
2
3
4
5
6
7 -XX:MaxGCPauseMillis=100 # 默认 200ms;值越小,GC 越频繁
-XX:G1HeapRegionSize=16m # 范围 1–32MB,必须是 2 的幂
-XX:G1MixedGCCountTarget=8 # Mixed GC 轮数目标(默认 8)
-XX:G1HeapWastePercent=5 # 允许的可回收空间浪费比例(默认 5%)
-XX:G1MixedGCLiveThresholdPercent=85 # 老年代 Region 存活对象 >85% 则不回收
并发标记原理
在应用程序运行的同时识别垃圾对象。其核心目标是 减少 Stop-The-World(STW)暂停时间,提升应用响应性。
在 GC 标记存活对象时,应用程序线程仍在运行:
- 新增对象(分配)
- 修改引用关系(如
a.ref = b)- 使原本存活的对象变为垃圾(如断开引用)
如果 GC 仅按某一时刻的快照标记,可能导致:
漏标(漏掉存活对象): 错误回收 , 应用崩溃(严重 Bug)
多标(标记了已死亡对象): 安全但效率低
因此,并发标记必须解决 “在并发修改下,如何保证标记结果的正确性”。
-
三色标记法(Tri-color Marking)
将对象分为三种状态(逻辑标记,非物理字段):
颜色 含义 白色 尚未被访问,可能是垃圾(GC 结束时若仍为白,则回收) 灰色 已被访问,自身存活,但其引用的对象尚未全部扫描 黑色 已被访问,且其所有引用对象也已扫描完毕,完全存活 不变性(Tri-color Invariant):在并发标记过程中,黑色对象不能直接引用白色对象。若违反,则白色对象可能被漏标(因黑色对象不会再被扫描)。
-
写屏障(Write Barrier)
为维护“黑色不能引用白色”的不变性,JVM 在每次修改引用字段时插入一段代码(即写屏障),用于拦截引用变更并采取保护措施。
并发标记的代价
CPU 开销:GC 线程与应用线程竞争 CPU。
内存开销:写屏障、Remembered Sets、标记位图等数据结构。
吞吐量下降:通常比 Parallel GC 低 10%–20%。
复杂性高:调试和调优难度大。
并发标记流程
标记阶段流程(基于 SATB):
-
Initial Mark(STW)
- 通常与一次 Young GC 合并执行(复用 STW)。
- 标记 GC Roots 直接可达对象。
-
Concurrent Mark(并发)
- GC 线程并发遍历对象图。
- 应用线程继续运行。
-
Remark(STW)
- 处理写屏障记录的变更。
- 完成标记,计算每个 Region 的存活对象比例(用于后续 Mixed GC 决策)。
-
Cleanup(STW + 并发)
- STW 阶段:回收全白的 Region(即时释放)。
- 并发阶段:重建 Remembered Sets 等内部结构。
G1 的写屏障策略:Snapshot-At-The-Beginning(SATB)
在并发标记开始瞬间,对整个堆做逻辑快照。
快照中存活的对象,即使后来变成垃圾,也视为存活(保守但安全)。
- 写屏障行为:当应用线程执行
A.ref = B(即 A 原本指向 C,现在指向 B),写屏障会记录被覆盖的旧引用 C。- 为什么有效?
- C 在快照时是存活的(因 A 到 C)。
- 即使 A 不再指向 C,C 仍可能被其他对象引用。
- 若 C 最终无引用,在下一次并发标记周期中回收(本次保守保留)。
- 优点:
- Remark 阶段更短(只需处理被覆盖的引用,无需重新扫描对象)。
- 与 G1 的 Region 化设计 和 增量回收 完美契合。
- 缺点:可能保留少量“浮动垃圾”(本次周期内死亡的对象),但安全。
vs CMS 并发标记
G1 的并发与CMS 并发标记的比较:
| 特性 | CMS | G1 |
|---|---|---|
| 写屏障策略 | Incremental Update | SATB(Snapshot-At-The-Beginning) |
| Remark 负担 | 重(需重新扫描对象) | 轻(仅处理被覆盖引用) |
| 内存碎片 | 有(Mark-Sweep) | 无(Evacuation + 压缩) |
| 停顿可预测性 | 差(Remark 时间不确定) | 好(目标 MaxGCPauseMillis) |
| 适用堆大小 | 中等(2–6GB) | 大堆(4GB+) |
