适用读者:要决定"用哪个 GC、堆给多大、调哪几个参数"的人。
前置:S05-J04。
实测环境:OpenJDK 21.0.12.1(G1 日志实测,2026-11-05)。
覆盖旧文:A-2315(1138) A-2634(552) A-2608(389) + 1016(100) 1011(86) 1018(82) 1132(167) 1951(136) 1624(142) 1025(83) 1625(137) 1637(90) 1626(155) 1007(67) 1020(74) = 3,398 点。
覆盖扩展:E-S05-015(收集器对比)、E-S05-016(GC 日志与停顿目标)。
上一篇:S05-J05 · 类加载与字节码执行
下一篇:S05-A02 · 线上故障诊断。
1. 一句话结论
选 GC 只有三个输入:堆多大、能容忍多长停顿、要不要极低延迟。 JDK 21 的默认答案(G1)能覆盖大多数场景。
| 收集器 | 算法 | 停顿 | 适合 |
|---|---|---|---|
| Serial | 复制/标记-压缩 | 长 | 小堆(< 数百 MB)、单核 |
| Parallel | 多线程复制 | 中 | 吞吐优先、批处理 |
| CMS | 并发标记清除 | 短 | JDK 14 已移除(别再用) |
| G1(默认) | Region 化 + 并发标记 | 可预测(MaxGCPauseMillis) |
中大型堆(数 GB~数十 GB) |
| ZGC(21 生产可用) | 染色指针 + 并发 | 亚毫秒级 | 超大堆、低延迟 |
| Shenandoah | 并发压缩 | 低 | 低延迟(非 Oracle 发行版常见) |
2. 实测:G1 日志里的关键信号
实测输出(-Xmx128m -Xlog:gc):
[0.629s] GC(0) Pause Young (G1 Humongous Allocation) 56M->55M(128M) 14.651ms
[0.786s] GC(4) Pause Young (Prepare Mixed) 125M->65M(128M) 3.998ms
[0.845s] GC(5) Pause Young (Mixed) (Evacuation Failure) 125M->70M(128M) 5.213ms
| 信号 | 含义 | 要不要管 |
|---|---|---|
Humongous Allocation |
大对象(> Region/2) | 频繁 → 查是否有大数组 |
Evacuation Failure |
晋升失败(目标区放不下) | 必须管:会导致 Full GC |
Pause Mixed |
混合回收(含老年代) | 频繁 → 老年代压力大 |
125M->65M(128M) |
回收前后/上限 | 回收后接近上限 → 该加堆 |
判据:"回收后还剩多少"比"停顿多长"更重要——回收后接近上限说明堆不够。
3. 选型流程(E-S05-015)
堆 < 4GB?
├── 是 → 吞吐优先?→ Parallel;低延迟?→ G1
└── 否 → 停顿目标 < 10ms?
├── 是 → ZGC(21+)
└── 否 → G1(设 -XX:MaxGCPauseMillis)
| 参数 | 作用 | 建议 |
|---|---|---|
-Xms/-Xmx |
初始/最大堆 | 设成相同(避免动态扩堆抖动) |
-XX:MaxGCPauseMillis |
G1 停顿目标 | 100–200ms 起步,别设太低(会导致频繁 Young GC) |
-XX:ParallelGCThreads |
并行线程数 | 默认按核数,容器里要手动限(避免超配) |
-XX:+HeapDumpOnOutOfMemoryError |
OOM 时 dump | 必开 |
-Xlog:gc*:file=... |
GC 日志 | 必开(带时间戳与文件轮转) |
4. 三个常见调优误区(E-S05-016)
| 误区 | 后果 |
|---|---|
把 MaxGCPauseMillis 设成 10ms |
G1 只能缩小 Young 区 → 更频繁 GC,总吞吐下降 |
| 堆设得极大(64G+)却不换收集器 | 单次回收变长;ZGC/G1 大堆更合适 |
不设 ParallelGCThreads 跑在容器里 |
线程数按宿主机核数算 → 超配、抢占 |
判据:调参前先看"GC 频率"与"回收后占用"两个数,否则是在猜。
5. 迁移与回滚
| 变更 | 迁移 | 回滚 |
|---|---|---|
| 换 GC(G1→ZGC) | 灰度一台 → 对比 P99 与 CPU → 全量 | 启动参数改回(无需改代码) |
| 调堆大小 | 分批次,观察 GC 频率 | 参数回退 |
| 换 JDK 大版本 | 见 S04-A01 检查单 | 镜像回退 |
铁律:GC 参数变更必须"可回滚且可观测"——没有 GC 日志的变更等于盲调。
6. 自检(架构师版)
- 选 GC 的三个输入是什么?
- 为什么 CMS 不该再用?
Evacuation Failure意味着什么?怎么处理?- 为什么
-Xms与-Xmx要设成相同? MaxGCPauseMillis设太低会怎样?
7. 下一篇
S05-A02 · 线上故障诊断:工具矩阵与 SOP
选型讲完,最后一篇讲"出事了怎么办":内存泄漏、CPU 飙高、频繁 Full GC 的三段式定界。
本篇属于系列《S05 · Java 并发与 JVM》。旧文来源:A-2315/2634/2608/1016/1011/1018/1132/1951/1624/1025/1625/1637/1626/1007/1020;扩展点 E-S05-015/016。GC 日志为 OpenJDK 21 实测。