JVMNotes

第 15 章:Serial 与 Parallel

zjc 于 2026-01-15 发布

这是《JVM 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Serial 和 Parallel 是经典分代收集器。Serial 适合小内存和单核场景,Parallel 追求吞吐量,适合批处理和对停顿不敏感的任务。理解它们有助于建立 GC 的基本模型。

15.1 Serial

Young: Serial Copying
Old:   Serial Mark-Sweep-Compact

启用:

java -XX:+UseSerialGC -jar app.jar

特点:

优点 缺点
实现简单 单线程
内存开销小 停顿随堆增大变长
小内存表现稳定 不适合大堆交互服务
客户端场景经典 多核利用不足

适用:

  1. 小容器;
  2. 低内存客户端;
  3. 单核环境;
  4. 简单任务;
  5. 极低内存开销优先。

15.2 Parallel

Young: Parallel Scavenge
Old:   Parallel Old

启用:

java -XX:+UseParallelGC -jar app.jar

特点:

优点 缺点
多线程标记复制 停顿可能较长
吞吐量高 停顿目标不如 G1 / ZGC
适合批处理 并发能力有限
资源集中执行 GC 交互服务长尾风险

吞吐目标参数:

-XX:GCTimeRatio=19
-XX:MaxGCPauseMillis=200
-XX:ParallelGCThreads=4

GCTimeRatio=19 表示目标 GC 时间约 1/(1+19)=5%。MaxGCPauseMillis 是软目标,不是硬保证。

15.3 分代布局

Heap
  |-- New Generation
  |   |-- Eden
  |   |-- Survivor 0
  |   +-- Survivor 1
  +-- Old Generation

参数:

-Xmn512m
-XX:NewRatio=2
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=15

示例:

NewRatio=2
  New : Old = 1 : 2

SurvivorRatio=8
  Eden : one survivor : other survivor = 8 : 1 : 1

15.4 Minor GC 触发

Eden 分配失败
  -> Minor GC
     -> 存活对象复制到 Survivor
        -> 年龄增加
           -> 达阈值或放不下晋升 Old

Full GC 常见触发原因:

  1. 老年代空间不足;
  2. 元空间不足;
  3. 晋升失败;
  4. GC 实现特定策略;
  5. 诊断命令触发。

不建议在业务代码中显式请求 Full GC。

15.5 停顿模型

停顿与以下因素相关:

  1. 堆大小;
  2. 存活对象数量;
  3. 引用密度;
  4. 卡表和根扫描;
  5. 线程数;
  6. CPU 资源;
  7. 内存带宽;
  8. 压缩范围。

大堆问题:

10G 堆,Old 中 6G 存活
  -> Full GC 需要标记和压缩大量对象
     -> 停顿可能达到秒级

15.6 GC 日志

Java 9 及以上统一日志:

java -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=20m \
     -XX:+UseParallelGC \
     -jar app.jar

Java 8:

java -XX:+PrintGCDetails \
     -XX:+PrintGCDateStamps \
     -Xloggc:gc.log \
     -jar app.jar

关键信息:

信息 说明
GC 前后堆 回收量
年轻代变化 Minor GC
老年代变化 晋升
user/sys/real CPU 与墙钟时间
worker 数 并行线程
停顿原因 触发条件

15.7 调优思路

吞吐优先调优:

1. 控制分配速率
2. 增大新生代,降低 Minor GC 频率
3. 避免过早晋升
4. 保证 Old 有余量
5. 合理设置并行线程
6. 用真实数据压测

风险:

操作 可能问题
过大新生代 单次 Minor GC 变长
过小新生代 频繁 GC 和晋升
Old 太小 Full GC
Old 太大 Full GC 停顿长
线程过多 CPU 竞争

15.8 与 G1 选择

场景 推荐
批处理、吞吐优先 Parallel
大堆交互服务 G1 / ZGC
小内存简单任务 Serial
需要可预测停顿 G1 / ZGC
现代微服务常见选择 G1 起步

选择流程:

明确 SLA
  -> 测量当前停顿和吞吐
     -> 先优化分配和缓存
        -> 小范围调参
           -> 对比 GC 日志
              -> 再评估切换 GC

15.9 生产案例

案例一:夜间批任务 Full GC

现象:批处理期间吞吐下降,Full GC 停顿 3 秒。

jstat -gcutil <pid> 1000

发现:大量对象直接晋升。

处理:

  1. 拆小批次;
  2. 流式读取;
  3. 复用缓冲;
  4. 增大新生代;
  5. 评估 Parallel 目标;
  6. 压测验证。

案例二:小容器停顿抖动

现象:256Mi 容器,交互服务频繁 GC。

方向:

  1. 降低内存分配;
  2. 评估 G1 是否更适合;
  3. 限制并发;
  4. 扩容;
  5. 优化缓存;
  6. 控制堆外内存。

本章小结

Serial 简单低开销,适合小内存;Parallel 多线程吞吐优先,适合批处理。两者都是经典分代模型,停顿会受堆大小、存活对象和 CPU 影响。调优先控制分配和晋升,再调堆比例,最后才考虑切换 GC。

思考题

  1. Parallel 的目标是什么?
  2. NewRatio 和 SurvivorRatio 如何计算?
  3. 为什么大堆 Full GC 停顿更长?
  4. 什么场景仍适合 Serial?
  5. 如何比较 Parallel 和 G1 的压测结果?