JVM垃圾回收器:Serial、ParNew、Parallel Scavenge 与 Parallel Old
在 Java 虚拟机(JVM)的内存管理中,垃圾回收(Garbage Collection, GC)是自动内存管理的核心机制。选择合适的垃圾回收器对应用程序的性能有着至关重要的影响。
尽管 G1、ZGC、Shenandoah 等新一代回收器已在 JDK 新版本中成为主流,但在大量遗留系统、嵌入式设备或特定场景下,四种经典回收器——Serial、ParNew、Parallel Scavenge 和 Parallel Old依然具有不可替代的价值。
本文将系统性地介绍这四大回收器的工作原理、核心特性、适用场景、配置参数及调优策略,帮助开发者精准选型,提升应用性能。
1. 串行回收器(Serial Collector)
1.1 概述
串行回收器是 JVM 中最古老、最简单的垃圾回收器,诞生于 Java 早期版本。它采用单线程执行所有 GC 工作,适用于资源受限或对响应时间要求不高的环境。
1.2 工作模式
1.2.1 新生代串行回收器(Serial)
- 回收算法:复制算法(Copying)
- 工作线程:单线程
- 关键特点:
- 采用Stop-The-World(STW)机制,GC 期间所有应用线程暂停。
- 内存分配使用指针碰撞(Bump the Pointer)技术,高效且无锁。
- 实现简单,额外内存开销最小(无需维护多线程状态或并发数据结构)。
- 适用场景:
- 客户端应用(如桌面软件、IDE 插件)
- 堆内存较小(几十 MB 到 200MB)
- 单核 CPU 或低功耗设备(如 IoT 设备)
1.2.2 老年代串行回收器(Serial Old)
- 回收算法:标记-整理算法(Mark-Compact)
- 工作线程:单线程
- 工作流程:
- 标记阶段:遍历对象图,标记所有存活对象。
- 整理阶段:将存活对象向一端移动,消除内存碎片。
- 特点:
- 同样采用 STW 机制。
- 由于老年代对象存活率高,整理过程耗时较长。
- 组合使用:通常与新生代 Serial 配合,形成完整的串行回收方案。
1.3 启用参数
# 启用串行垃圾回收器(新生代 + 老年代)-XX:+UseSerialGC✅ 该参数会同时启用
Serial(新生代)和Serial Old(老年代)。
1.4 优缺点分析
| 优点 | 缺点 |
|---|---|
| 实现简单,稳定性高 | 暂停时间长,用户体验差 |
| 内存开销最小 | 无法利用多核 CPU 并行能力 |
| 适合单核环境 | 不适用于高并发或实时系统 |
2. 新生代并行回收器(ParNew)
2.1 概述
ParNew 是Serial 收集器的多线程并行版本,专为新生代设计。其核心思想是:在多核 CPU 上,并行执行 GC 以缩短 STW 时间。
📌 注意:ParNew 仅作用于新生代,老年代需搭配其他回收器(如 CMS 或 Serial Old)。
2.2 核心特性
- 多线程并行回收:使用多个 GC 线程同时清理 Eden 和 Survivor 区。
- 与 CMS 高度协同:在 JDK 8 及之前,ParNew 是 CMS 的默认新生代搭档。
- 工作模式:
- 算法:复制算法(与 Serial 相同)
- 机制:仍为 STW,但因并行执行,停顿时间显著短于 Serial
- 线程数控制:
- 默认值 ≈ CPU 核数(≤8 时等于核数;>8 时按公式
3 + (5 * CPU_Count) / 8计算)
- 默认值 ≈ CPU 核数(≤8 时等于核数;>8 时按公式
2.3 工作原理(文字描述)
当 Eden 区满时,ParNew 触发 Minor GC:
- 所有应用线程暂停(STW)。
- 多个 GC 线程并行扫描 Eden 和 From Survivor 区。
- 存活对象被复制到 To Survivor 区(或直接晋升到老年代)。
- 清空 Eden 和 From Survivor,交换 Survivor 角色。
- 应用线程恢复运行。
💡 由于新生代对象“朝生夕死”,ParNew 能高效完成回收。
2.4 配置参数
# 启用 ParNew(JDK 8 及更早)-XX:+UseParNewGC# 设置 GC 线程数(建议 ≤ CPU 核数)-XX:ParallelGCThreads=4⚠️重要提示:
-XX:+UseParNewGC在JDK 9+ 已被移除(因 CMS 被废弃)。- 单独使用该参数时,老年代会回退到Serial Old,但这不是推荐用法。
- 正确用法是配合 CMS:
-XX:+UseConcMarkSweepGC(自动启用 ParNew)。
2.5 适用场景
- 多核服务器环境
- Web 应用、微服务(JDK 8 时代)
- 对暂停时间敏感,但可接受一定吞吐量损失的系统
3. 吞吐量优先回收器(Parallel Scavenge / ParallelGC)
3.1 概述
Parallel Scavenge(常称 ParallelGC)是JDK 8 Server 模式的默认垃圾回收器,其设计目标不是“停顿短”,而是最大化应用程序的吞吐量。
📌 吞吐量 = 应用程序运行时间 / (应用程序运行时间 + GC 时间)
3.2 设计目标
- 高吞吐量:让 CPU 尽可能多地执行业务逻辑。
- 自适应调节:根据运行时数据动态调整堆结构和 GC 策略。
3.3 新生代 ParallelGC 特性
- 回收算法:复制算法
- 并行执行:多线程清理新生代
- 自适应策略(AdaptiveSizePolicy):
- 自动调整 Eden 与 Survivor 区比例
- 动态计算对象晋升老年代的年龄阈值(MaxTenuringThreshold)
- 权衡:可能产生较长的单次 GC 暂停,但整体吞吐量更高
3.4 老年代 ParallelOldGC 特性
- 回收算法:标记-整理算法
- 并行执行:多线程并行完成标记与整理
- 配合使用:与 Parallel Scavenge 形成完整的高吞吐量回收方案
3.5 核心参数
# 启用 ParallelGC(JDK 8 默认)-XX:+UseParallelGC# 显式启用 ParallelOld(通常无需指定)-XX:+UseParallelOldGC# 目标最大暂停时间(毫秒,非硬性保证)-XX:MaxGCPauseMillis=100# 吞吐量目标:GC 时间占比 = 1/(1+n)# n=19 → GC 占比 ≤5%,吞吐量 ≥95%-XX:GCTimeRatio=19# GC 线程数-XX:ParallelGCThreads=8# 启用自适应大小调整(默认开启)-XX:+UseAdaptiveSizePolicy3.6 工作流程示例(Minor GC)
- Eden 区满,触发 Minor GC。
- 应用线程暂停(STW)。
- 多线程并行复制存活对象到 Survivor 或老年代。
- Eden 清空,应用恢复。
- 若老年代空间不足,可能触发 Full GC(由 Parallel Old 执行)。
4. 四种回收器对比总结
| 特性 | 串行回收器 | ParNew | ParallelGC | ParallelOldGC |
|---|---|---|---|---|
| 回收区域 | 新生代 + 老年代 | 新生代 | 新生代 | 老年代 |
| 线程模式 | 单线程 | 多线程 | 多线程 | 多线程 |
| 回收算法 | 复制 / 标记-整理 | 复制 | 复制 | 标记-整理 |
| 设计目标 | 简单稳定 | 降低暂停时间 | 高吞吐量 | 高吞吐量 |
| 暂停时间 | 长 | 较短 | 可能较长 | 可能较长 |
| 吞吐量 | 低 | 中等 | 高 | 高 |
| 适用场景 | 单核/客户端 | 多核 + CMS | 后台计算 | 后台计算 |
| 内存开销 | 最小 | 中等 | 中等 | 中等 |
| JDK 支持 | 所有版本 | JDK 8 及更早 | JDK 8 默认 | JDK 6u14+ |
🔍日志识别技巧:
[DefNew]→ Serial[ParNew]→ ParNew[PSYoungGen]→ Parallel Scavenge[ParOldGen]→ Parallel Old[Tenured]→ Serial Old
5. 如何选择合适的垃圾回收器
5.1 选择指南
| 应用类型 | 推荐 GC 组合 | 理由 |
|---|---|---|
| 客户端/桌面应用 | -XX:+UseSerialGC | 内存小、单核、简单稳定 |
| Web 应用/微服务(JDK 8) | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC | 降低 GC 暂停,提升响应速度 |
| 后台计算/批处理 | -XX:+UseParallelGC | 最大化吞吐量,适合计算密集型任务 |
| 内存 < 100MB | -XX:+UseSerialGC | 额外开销最小 |
5.2 参数调优示例
吞吐量优先的批处理应用
java -Xmx4g -Xms4g\-XX:+UseParallelGC\-XX:MaxGCPauseMillis=100\-XX:GCTimeRatio=19\-XX:+PrintGCDetails\-jar batch-app.jar响应时间敏感的 Web 应用(JDK 8)
java -Xmx2g -Xms2g\-XX:+UseConcMarkSweepGC\-XX:CMSInitiatingOccupancyFraction=75\-XX:+UseCMSInitiatingOccupancyOnly\-XX:+PrintGCDetails\-jar web-app.jar💡 注意:
-XX:+UseParNewGC在配合 CMS 时可省略,因 CMS 会自动启用它。
6. 监控与诊断
6.1 常用监控命令
# 输出详细 GC 日志-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log# 实时监控(每秒刷新)jstat -gc<pid>1000# 图形化工具jvisualvm6.2 关键指标解读
| 指标 | 说明 |
|---|---|
| 吞吐量 | 应用运行时间占总时间的比例(越高越好) |
| 暂停时间 | 单次 GC 导致应用停顿的时间(越低越好) |
| GC 频率 | 单位时间内 GC 次数(过高可能内存不足) |
| 内存占用 | 堆内存使用情况(观察是否频繁 Full GC) |
7. 总结与展望
本文详细介绍了四种经典的垃圾回收器:
- Serial:简单稳定的基石,适合资源受限环境。
- ParNew:多核时代的新生代优化,与 CMS 默契配合。
- ParallelGC:吞吐量优先的默认选择,适合后台计算。
- ParallelOldGC:ParallelGC 的老年代搭档,提供完整高吞吐方案。
🔄演进趋势:
从 JDK 9 开始,G1 成为默认 GC;JDK 11 引入ZGC(亚毫秒停顿);JDK 17+ 进一步推广虚拟线程(Project Loom),改变并发模型。
尽管如此,在嵌入式、小型服务或旧系统维护中,这四大经典回收器仍有其不可替代的价值。理解它们的原理与边界,是 JVM 调优的第一步。
Q1:为什么ParallelGC不适合Web应用?
A:Web应用对响应时间敏感,ParallelGC的单次停顿可能达到几百毫秒,导致用户体验差。G1/ZGC的停顿时间通常控制在10-200ms内。
Q2:如何判断该选择哪种GC?
A:遵循以下决策流程:
1. 确定需求:吞吐量优先 vs 低延迟优先 2. 评估硬件:单核/小内存 → Serial;多核/大内存 → Parallel或G1 3. 考虑JDK版本:JDK8 → ParallelGC/CMS;JDK11+ → G1;JDK15+ → ZGC 4. 测试验证:用真实负载测试不同GC的表现Q3:GC日志中的"user"、“sys”、"real"时间代表什么?
[Times: user=0.19 sys=0.00 real=0.18 secs] - user: GC线程消耗的CPU时间总和 - sys: 操作系统调用和等待时间 - real: 应用程序实际暂停时间(墙钟时间) 对于ParallelGC:real < user (多核并行) 对于SerialGC:real ≈ user + sys (单核串行)