几种常用垃圾收集器(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 阶段:

    1. Initial Mark(STW,快)
    2. Concurrent Mark(并发)
    3. Remark(STW,较慢)
    4. 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)暂停时间,提升应用响应性。

标记过程(基于增量更新):

  1. Initial Mark(STW)

    • 暂停所有应用线程。
    • 从 GC Roots(栈、寄存器、静态变量等)出发,标记直接可达对象为灰色。
    • 非常快(仅扫描根集合)。
  2. Concurrent Mark(并发)

    • 应用线程恢复运行。
    • GC 线程从灰色对象出发,深度优先遍历引用图,将灰色变黑,白色变灰。
    • 期间应用线程可修改引用
  3. Remark(STW)

    • 再次暂停应用线程。
    • 处理并发期间被写屏障记录的引用变更(见下文)。
    • 完成最终标记,所有存活对象变为黑色
  4. 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)

  • 多标(标记了已死亡对象): 安全但效率低

因此,并发标记必须解决 “在并发修改下,如何保证标记结果的正确性”

  1. 三色标记法(Tri-color Marking)

    将对象分为三种状态(逻辑标记,非物理字段):

    颜色 含义
    白色 尚未被访问,可能是垃圾(GC 结束时若仍为白,则回收)
    灰色 已被访问,自身存活,但其引用的对象尚未全部扫描
    黑色 已被访问,且其所有引用对象也已扫描完毕完全存活

    不变性(Tri-color Invariant):在并发标记过程中,黑色对象不能直接引用白色对象。若违反,则白色对象可能被漏标(因黑色对象不会再被扫描)。

  2. 写屏障(Write Barrier)

    为维护“黑色不能引用白色”的不变性,JVM 在每次修改引用字段时插入一段代码(即写屏障),用于拦截引用变更并采取保护措施。

并发标记的代价

  • CPU 开销:GC 线程与应用线程竞争 CPU。

  • 内存开销:写屏障、Remembered Sets、标记位图等数据结构。

  • 吞吐量下降:通常比 Parallel GC 低 10%–20%。

  • 复杂性高:调试和调优难度大。

并发标记流程

标记阶段流程(基于 SATB):

  1. Initial Mark(STW)

    • 通常与一次 Young GC 合并执行(复用 STW)。
    • 标记 GC Roots 直接可达对象。
  2. Concurrent Mark(并发)

    • GC 线程并发遍历对象图。
    • 应用线程继续运行。
  3. Remark(STW)

    • 处理写屏障记录的变更。
    • 完成标记,计算每个 Region 的存活对象比例(用于后续 Mixed GC 决策)。
  4. 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+)

ZGC 收集器

Shenandoah 收集器