在 JVM 的垃圾回收机制中,垃圾收集器是内存回收算法的具体实现者。从 JDK 1.0 的 Serial 收集器到 JDK 17 的 ZGC,垃圾收集器的技术演进始终围绕着三个核心目标:缩短停顿时间、提高吞吐量、支持更大堆内存。本文将系统剖析 7 种主流垃圾收集器的工作原理、优缺点及适用场景,为不同应用场景的 GC 参数配置提供实战指南。
一、垃圾收集器的分类:并行与并发的核心区别
在深入了解具体收集器之前,需要明确两个关键概念:
- 并行(Parallel):指多条 GC 线程同时工作,此时用户线程处于暂停状态(Stop The World)。并行收集能提高 GC 效率,但会导致应用卡顿。
- 并发(Concurrent):指 GC 线程与用户线程同时工作,用户线程无需暂停或仅短暂暂停。并发收集能减少停顿时间,但会增加 CPU 开销和内存占用。
所有垃圾收集器都基于分代收集理论设计(新生代 + 老年代),但不同收集器对新生代和老年代的回收策略存在显著差异。以下是 7 种收集器的关系图谱:
(示意图:垃圾收集器的分代与组合关系,如 Serial GC 对应 Serial Old GC,Parallel Scavenge 对应 Parallel Old GC 等)
二、新生代收集器:处理 “朝生夕死” 的对象
新生代对象的特点是存活时间短、存活率低(约 98% 的对象会在第一次 GC 中被回收),因此新生代收集器均采用复制算法(高效处理短生命周期对象)。
2.1 Serial 收集器:单线程的 “入门级” 实现
核心特性:
- 单线程执行 GC,收集时必须暂停所有用户线程(Stop The World);
- 新生代采用复制算法,老年代默认搭配 Serial Old 收集器(标记 - 整理算法)。
工作流程:
- 当 Eden 区满时,触发 Minor GC,Serial 收集器启动单线程;
- 暂停所有用户线程,标记新生代中的存活对象;
- 将存活对象复制到 Survivor 区,清空 Eden 区和原 Survivor 区;
- 恢复用户线程执行。
优缺点:
- 优点:实现简单,内存占用小,无线程交互开销,在单 CPU 环境下效率较高;
- 缺点:GC 时用户线程暂停时间随堆内存增大而增加,无法利用多核 CPU 优势。
适用场景:
- 客户端应用(如桌面程序、工具类软件);
- 堆内存较小(<1GB)的场景;
- JVM 参数:-XX:+UseSerialGC(新生代 Serial + 老年代 Serial Old)。
2.2 Parallel Scavenge 收集器:吞吐量优先的并行实现
核心特性:
- 多线程并行执行 GC,仍会产生 Stop The World;
- 新生代采用复制算法,老年代搭配 Parallel Old 收集器(标记 - 整理算法);
- 目标是提高吞吐量(吞吐量 = 用户线程执行时间 /(用户线程时间 + GC 时间))。
关键参数:
- -XX:ParallelGCThreads=n:设置 GC 线程数(默认与 CPU 核心数相同);
- -XX:MaxGCPauseMillis=n:设置最大 GC 停顿时间(默认 - 1,无限制,值越小吞吐量可能越低);
- -XX:GCTimeRatio=n:设置吞吐量比例(默认 99,允许 GC 时间占比 = 1/(1+n))。
实战案例:
# 配置Parallel Scavenge收集器,最大停顿时间100ms,吞吐量比例99(允许1%的GC时间)
java -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:GCTimeRatio=99 -jar app.jar
优缺点:
- 优点:多线程并行收集,吞吐量高,适合计算密集型应用;
- 缺点:停顿时间较长,不适用于响应时间敏感的场景(如 Web 服务)。
适用场景:
- 后台计算、数据分析等对吞吐量要求高,对响应时间不敏感的应用;
- 多核 CPU、堆内存中等(1GB~10GB)的场景。
2.3 ParNew 收集器:与 CMS 配合的并行收集器
核心特性:
- 多线程并行执行 GC(与 Parallel Scavenge 类似),但不追求吞吐量优化;
- 唯一能与 CMS 收集器(老年代)配合工作的新生代收集器。
与 Parallel Scavenge 的区别:
- ParNew 可通过-XX:ParallelGCThreads控制线程数,但无MaxGCPauseMillis和GCTimeRatio参数(不优化吞吐量);
- ParNew 更注重与 CMS 的兼容性,而 Parallel Scavenge 专注于吞吐量。
适用场景:
- 需要搭配 CMS 收集器的场景(如 Web 应用);
- JVM 参数:-XX:+UseParNewGC(新生代 ParNew + 老年代默认 CMS)。
三、老年代收集器:处理长生命周期对象
老年代对象的特点是存活时间长、存活率高,因此老年代收集器多采用标记 - 清除或标记 - 整理算法(减少内存碎片,适合长生命周期对象)。
3.1 Serial Old 收集器:Serial 的老年代搭档
核心特性:
- 单线程执行 GC,基于标记 - 整理算法;
- 主要作为 Serial 收集器和 CMS 收集器的 “应急方案”(如 CMS 发生 Concurrent Mode Failure 时)。
工作流程:
- 当老年代内存不足时,触发 Major GC,Serial Old 启动单线程;
- 暂停所有用户线程,标记老年代中的存活对象;
- 将存活对象向内存一端移动,清理边界外的内存(消除碎片);
- 恢复用户线程执行。
优缺点:
- 优点:实现简单,内存占用小;
- 缺点:单线程效率低,停顿时间长(尤其在大堆内存场景)。
适用场景:
- 客户端应用或小内存服务(堆内存 < 1GB);
- 作为 CMS 收集器的后备方案(不推荐主动使用)。
3.2 Parallel Old 收集器:Parallel Scavenge 的老年代搭档
核心特性:
- 多线程并行执行 GC,基于标记 - 整理算法;
- 与 Parallel Scavenge 组成 “吞吐量优先” 的全并行收集方案。
与 Serial Old 的区别:
- 多线程并行收集,适合多核 CPU;
- 标记 - 整理阶段采用多线程协作,效率更高。
适用场景:
- 与 Parallel Scavenge 配合,用于追求高吞吐量的后台应用;
- JVM 参数:-XX:+UseParallelOldGC(需与-XX:+UseParallelGC同时启用)。
3.3 CMS 收集器:追求最短停顿的并发收集器
CMS(Concurrent Mark Sweep)是 JDK 5 引入的首款并发收集器,其设计目标是最短化 GC 停顿时间,非常适合 Web 应用等对响应时间敏感的场景。
核心特性:
- 基于标记 - 清除算法,实现 GC 线程与用户线程的并发执行;
- 老年代收集器,需搭配 ParNew 或 Serial 收集器作为新生代收集器。
工作流程(四阶段):
- 初始标记(Initial Mark):
-
- 暂停用户线程,标记 GC Roots 直接关联的对象(如老年代中被新生代引用的对象);
-
- 耗时极短(通常毫秒级)。
- 并发标记(Concurrent Mark):
-
- 恢复用户线程,GC 线程与用户线程并发执行;
-
- 遍历老年代,标记所有可达对象(从初始标记的对象出发);
-
- 耗时最长(数秒级),但用户线程无需暂停。
- 重新标记(Remark):
-
- 暂停用户线程,修正并发标记期间因用户线程操作导致的标记变动(如对象引用被修改);
-
- 耗时短于初始标记(通常十毫秒级)。
- 并发清除(Concurrent Sweep):
-
- 恢复用户线程,GC 线程与用户线程并发执行;
-
- 清理被标记的对象,释放内存;
-
- 无停顿,但会产生内存碎片。
关键参数:
- -XX:+UseConcMarkSweepGC:启用 CMS 收集器;
- -XX:CMSInitiatingOccupancyFraction:老年代占用率阈值(默认 68%,达到该值触发 CMS);
- -XX:+UseCMSCompactAtFullCollection:Full GC 后进行内存整理(解决碎片问题,默认启用);
- -XX:CMSFullGCsBeforeCompaction:指定多少次 Full GC 后进行一次整理(默认 0,即每次都整理)。
实战案例:
# 启用CMS收集器,老年代占用率达80%时触发GC,每3次Full GC后整理一次内存
java -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:CMSFullGCsBeforeCompaction=3 -jar app.jar
优缺点:
- 优点:并发收集,停顿时间短(毫秒级),响应速度快;
- 缺点:
-
- CPU 敏感(并发阶段占用部分 CPU 资源,可能导致应用吞吐量下降);
-
- 内存碎片(标记 - 清除算法),可能触发频繁 Full GC;
-
- 需要预留内存(并发阶段用户线程仍在分配内存,需避免 OOM)。
适用场景:
- 互联网应用、Web 服务等对响应时间要求高的场景;
- 堆内存中等(2GB~16GB),CPU 核心数较多的服务器。
3.4 G1 收集器:跨代收集的 “全能选手”
G1(Garbage-First)是 JDK 7 引入的跨代收集器,打破了传统的 “新生代 + 老年代” 物理隔离,将堆内存划分为多个大小相等的独立区域(Region),每个 Region 既可作为 Eden 区、Survivor 区,也可作为老年代区。
核心特性:
- 基于 “区域化分代式” 设计,兼顾新生代和老年代回收;
- 采用复制算法(Region 间复制存活对象)和标记 - 整理算法(Region 内整理);
- 可预测的停顿时间(通过-XX:MaxGCPauseMillis指定目标停顿时间)。
工作流程(五阶段):
- 初始标记(Initial Mark):
-
- 暂停用户线程,标记 GC Roots 直接关联的对象;
-
- 耗时短,通常与 Minor GC 同步执行。
- 并发标记(Concurrent Mark):
-
- 恢复用户线程,遍历所有 Region,标记可达对象;
-
- 记录 Region 的存活对象比例(用于后续筛选)。
- 最终标记(Final Mark):
-
- 暂停用户线程,修正并发标记期间的标记变动;
-
- 采用 “快照 - Atlético” 算法,高效处理并发修改。
- 筛选回收(Live Data Counting and Evacuation):
-
- 暂停用户线程,根据 Region 的存活对象比例和MaxGCPauseMillis,选择回收价值最高的 Region(Garbage-First 的由来);
-
- 将选中 Region 的存活对象复制到空 Region,同时清理原 Region(消除碎片)。
关键参数:
- -XX:+UseG1GC:启用 G1 收集器;
- -XX:MaxGCPauseMillis=n:目标最大停顿时间(默认 200ms);
- -XX:G1HeapRegionSize=n:设置 Region 大小(1MB~32MB,需为 2 的幂次方);
- -XX:G1NewSizePercent和-XX:G1MaxNewSizePercent:新生代占比的上下限(默认 5%~60%)。
实战案例:
# 启用G1,目标停顿时间100ms,Region大小4MB
java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1HeapRegionSize=4m -jar app.jar
优缺点:
- 优点:
-
- 支持大堆内存(10GB~100GB),停顿时间可控;
-
- 内存碎片少(Region 复制 + 整理);
-
- 兼顾吞吐量和响应时间。
- 缺点:
-
- 内存占用高(需要维护 Region 的元数据);
-
- 并发阶段 CPU 开销大。
适用场景:
- 堆内存较大(>10GB)的服务端应用(如电商平台、分布式服务);
- 对停顿时间敏感且需要可预测性的场景。
3.5 ZGC:超低延迟的新一代收集器
ZGC(Z Garbage Collector)是 JDK 11 引入的实验性收集器,JDK 15 正式转正,设计目标是停顿时间不超过 10ms,支持 TB 级堆内存。
核心特性:
- 基于 Region 设计(支持动态 Region 大小);
- 采用 “颜色指针” 技术(通过指针标记对象状态,避免停顿);
- 并发执行所有阶段(初始标记、并发标记、重新映射等),几乎无停顿。
革命性技术:
- 颜色指针:将对象的标记信息存储在指针的高几位(如 64 位系统中,用 4 位存储状态),无需遍历对象修改标记位;
- 读屏障:当用户线程访问对象时,通过读屏障自动处理指针的状态转换(如将旧地址映射到新地址)。
适用场景:
- 超大堆内存(>100GB)的应用(如大数据平台、内存数据库);
- 对延迟要求极高的场景(如高频交易系统);
- JVM 参数:-XX:+UseZGC(JDK 15 + 默认启用)。
3.6 Shenandoah 收集器:RedHat 的并发收集方案
Shenandoah 是 RedHat 开发的开源收集器(JDK 12 引入),与 ZGC 类似,主打低延迟和大堆支持,但实现细节存在差异:
- 不依赖颜色指针(因专利限制),采用 “连接矩阵” 记录对象引用关系;
- 重新映射阶段采用并发处理(与 ZGC 的并发重新映射类似);
- 停顿时间与堆大小无关(主要取决于存活对象数量)。
适用场景:
- 与 ZGC 重叠,适合大堆内存和低延迟场景;
- JVM 参数:-XX:+UseShenandoahGC(需使用 OpenJDK 的特定版本)。
四、收集器的实战选择:从场景出发的决策指南
不同收集器的特性差异决定了它们的适用场景,以下是基于应用类型的选择建议:
应用类型 |
核心需求 |
推荐收集器 |
典型 JVM 参数 |
客户端应用 |
简单、低内存占用 |
Serial GC |
-XX:+UseSerialGC |
后台计算 |
高吞吐量 |
Parallel GC |
-XX:+UseParallelGC -XX:+UseParallelOldGC |
Web 应用 |
低延迟、中等堆内存 |
CMS + ParNew |
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC |
大堆服务(10-100GB) |
可控延迟、高内存利用率 |
G1 GC |
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 |
超大堆服务(>100GB) |
超低延迟 |
ZGC/Shenandoah |
-XX:+UseZGC或-XX:+UseShenandoahGC |
五、GC 日志分析:验证收集器的工作状态
无论选择哪种收集器,都需要通过 GC 日志验证其工作状态。开启 GC 日志的参数:
# 输出详细GC日志,包含时间戳、堆占用变化、停顿时间等
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar
关键指标解读:
- GC 时间:Minor GC 和 Major GC 的耗时,需与应用的响应时间要求对比;
- 吞吐量:(总运行时间 - 总 GC 时间)/ 总运行时间,需达到业务预期(如 99%);
- 内存碎片:老年代的使用率与实际可用内存的差值(碎片率 = 1 - 可用内存 / 总内存),碎片率过高需考虑标记 - 整理算法的收集器。
六、总结与展望
垃圾收集器的技术演进见证了 JVM 对性能的极致追求:从 Serial 的单线程停顿到 ZGC 的微秒级延迟,从 GB 级堆内存到 TB 级支持,每一步突破都为 Java 应用开辟了新的可能性。
选择收集器的核心原则是匹配应用特性:计算密集型应用优先考虑 Parallel GC,延迟敏感型应用选择 G1 或 ZGC,超大堆场景则非