Serial 与 Parallel:吞吐型垃圾收集器#
版本信息
- 覆盖:Serial GC | Parallel GC(Parallel Scavenge + Parallel Old)
- 默认:Serial 是 client 模式/小堆默认;Parallel 是 JDK 8 服务端默认(JEP 248 后 JDK 9 起改默认 G1)
- MXBean 名:Serial →
Copy/MarkSweep Compact;Parallel →PS Scavenge/PS MarkSweep(官方标准名)
如果你从 JDK 8 升上来,你原来用的默认 GC 就是 Parallel。它简单、吞吐高,代价是 Full GC 整堆、停顿较长。本页讲清它的原理和参数——以及「升级后我还该不该用它」。
设计目标:吞吐优先#
Serial 与 Parallel 都属于吞吐型收集器,回收时全程 STW,不与应用线程并发:
- Serial:单线程回收。简单、省内存,适合小堆、单核或资源受限(client 应用、IoT、单核容器)。
- Parallel(Parallel Scavenge + Parallel Old):多线程并行回收,JDK 8 服务端默认。它有一套自适应(ergonomics)机制,能根据
-XX:MaxGCPauseMillis/-XX:GCTimeRatio目标自动调整年轻代大小,追求最大吞吐。
它们的共同代价:没有并发回收,Full GC 是整堆 STW,堆越大停顿越长。这正是后来 G1/ZGC 要解决的。
工作原理:分代 + 全 STW#
graph TD
A["应用分配对象"] --> E["Eden 区满"]
E --> M["Minor GC(STW)<br/>复制存活对象到 Survivor"]
M --> S["Survivor 区"]
S -->|"多轮存活后晋升"| O["老年代"]
O -->|"老年代占用达阈值"| F["Full GC(STW)<br/>标记-压缩,整理全堆"]
style M fill:#bbdefb
style F fill:#ffcdd2
- 年轻代用复制算法:Eden 满 → Minor GC(STW),存活对象拷进 Survivor,重复存活足够多次(由
-XX:MaxTenuringThreshold控制)的对象晋升老年代。 - 老年代用标记-压缩(mark-compact):标记可达对象、清理不可达、再滑动整理,避免碎片(这点强于不整理的 CMS)。
- Serial 与 Parallel 的算法骨架相同,差别只在单线程 vs 多线程。所有阶段都是 STW——「并行」是指多个 GC 线程同时干活,应用线程照常暂停。
JVM 配置参数#
启用#
java -XX:+UseSerialGC -Xmx512m -jar app.jar # Serial:单线程
java -XX:+UseParallelGC -Xmx4g -jar app.jar # Parallel:JDK 8 server 默认,多线程
Parallel 关键调优参数#
| 参数 | 含义 | 默认 | 备注 |
|---|---|---|---|
-XX:MaxGCPauseMillis=<ms> |
期望最大停顿目标 | 不限制 | ergonomics 据此缩小年轻代;设得太激进会频繁 Minor GC |
-XX:GCTimeRatio=<n> |
GC 时间占比目标 = 1/(1+n) |
99(≈1%) | 越大越求吞吐;调大减少 GC 频率 |
-XX:+UseAdaptiveSizePolicy |
自适应调整 Eden/Survivor/年轻代大小 | 开 | 与手动 -Xmn 冲突:手动固定后自适应失效 |
-XX:ParallelGCThreads=<n> |
STW 阶段的并行 GC 线程数 | ≈ 物理 CPU 核数 | 容器内注意别超配,否则上下文切换反噬 |
-XX:NewRatio=<r> |
老年代 : 年轻代 = r : 1 |
2(即 2:1) | 想加大年轻代可调小,如 -XX:NewRatio=1 |
-XX:SurvivorRatio=<r> |
Eden : 每个 Survivor = r : 1 |
8 | 调大给 Eden 更多空间 |
-XX:MaxTenuringThreshold=<n> |
对象晋升老年代的存活轮数上限 | 15 | 调小让对象更快进老年代 |
# 典型:吞吐型批处理,求高吞吐、允许较长停顿
java -XX:+UseParallelGC -Xmx8g \
-XX:GCTimeRatio=49 \ # GC 占比目标 ≈ 2%,更重吞吐
-XX:MaxGCPauseMillis=500 \ # 停顿软目标 500ms
-XX:ParallelGCThreads=8 \
-jar batch-job.jar
适用场景#
- Serial:堆很小(一般 < 100MB)、单核 CPU、client 桌面程序、资源受限设备;或只想用最少内存跑个 JVM的场景。
- Parallel:CPU 密集、批处理、离线数据分析、ETL——对单次停顿不敏感、但求稳态吞吐最大。没有交互、没有实时 SLA 的后台任务用它很合适。
与前代 GC 对比:Serial → Parallel#
Parallel 是 Serial 的多线程并行升级——同为分代 + 全 STW、同为复制 / 标记压缩,但回收时用多个 GC 线程而非单线程,多核机器上吞吐显著提升。两者取舍看堆大小与 CPU 核数。
java -XX:+UseSerialGC -Xmx512m -jar app.jar # 单线程回收,省内存、适合单核
java -XX:+UseParallelGC -Xmx8g \ # 多线程并行,吞吐优先
-XX:GCTimeRatio=49 -jar batch.jar
小堆 / 单核用 Serial(无多线程开销);多核吞吐型用 Parallel。注意现代 JDK(9+)默认已是 G1,需
-XX:+UseParallelGC显式切回。
常见坑 / 最佳实践#
- 升级后别无脑加
-XX:+UseParallelGC:JDK 9+ 默认 G1 已对多数场景更好。除非吞吐型负载有实测收益,否则用默认 G1。 MaxGCPauseMillis是软目标,不是硬保证:Parallel 会尽量靠近,设得过小只会让年轻代缩到很小、Minor GC 更频繁,吞吐反降。- 别同时设
-Xmn和自适应:手动固定年轻代(-Xmn/-XX:NewRatio)会让-XX:+UseAdaptiveSizePolicy失去作用,两套调整打架。 - 容器内注意
ParallelGCThreads:默认按物理核数,在 CPU 限额的容器里可能远超配额,建议显式设成容器限额核数。 - Full GC 整堆长停顿是它的硬伤:堆一大、对象一多,Full GC 停顿可能到秒级——对延迟敏感就该换 G1/ZGC,而非死调 Parallel。
小结#
Serial 与 Parallel 是「吞吐优先、回收全 STW」的经典选择。Serial 适合小堆/单核,Parallel 曾是 JDK 8 服务端默认、适合批处理等吞吐型负载。但它的 Full GC 长停顿在交互/在线场景是硬伤——这正是 G1(停顿可控)和 ZGC/Shenandoah(亚毫秒)登场的原因。
参考#
- JEP 248: Make G1 the Default GC(JDK 9,Parallel 不再是默认)
- Oracle: Parallel GC 调优(Java 21)
- 概览与选型:垃圾收集器专题 | 下一站:CMS(已移除)