1.2.1 Java 性能与调优 · JVM 内存模型与 GC 算法全对比
JVM 内存模型 + 7 大 GC 收集器对比 + 选型决策。
1. 选题理由
Java 后端面试 90% 必问 GC,线上 OOM/Full GC 故障 100% 离不开内存模型。一句话定位:这篇把 JVM 运行时数据区、对象存活判定、4 类算法思想、7 款收集器横向对比、参数调优模板压在一篇里,翻这一篇就够。
2. JVM 内存模型
按《Java 虚拟机规范》(JSR-337 第 2 版) 划分,线程私有区随线程生死,线程共享区由 GC 接管。
| 区域 | 归属 | 异常 | 关键参数 |
|---|---|---|---|
| PC 寄存器 / 虚拟机栈 / 本地方法栈 | 线程私有 | StackOverflowError | -Xss 默认 512K~1M |
| 堆 Heap | 线程共享 | OutOfMemoryError | -Xms -Xmx |
| 元空间 Metaspace | 线程共享(本地内存) | OutOfMemoryError | -XX:MaxMetaspaceSize |
| 直接内存 DirectMemory | NIO 堆外 | OOM,但较小堆可见 | -XX:MaxDirectMemorySize |
// 验证堆外内存存在 — 分配 1G 但不释放
ByteBuffer.allocateDirect(1024 * 1024 * 1024);
// 堆可能只占 200M,实际 RSS 已达 1.2G+
调研依据:JEP 387(JDK 16) 把 ZGC 的 metaspace 纳入并发管理;JDK 8 用 PermGen,JDK 8+ 改为 Metaspace 走本地内存,默认无上限是历史 OOM 重灾区。
3. 对象判定与四大引用
判定算法:从 GC Roots(栈帧局部变量、静态引用、JNI 句柄、活跃线程)出发,可达性分析(Reachability Analysis),不可达即回收候选。finalize() 只救一次且不保证执行,JDK 9 起标记 `@Deprecated。
| 引用类型 | 回收时机 | 典型场景 |
|---|---|---|
| Strong 强 | GC Roots 不可达 | 普通 new Object() |
| Soft 软 | 内存不足前(OOM 前) | 图片缓存 |
| Weak 弱 | 下一次 GC 必回收 | WeakHashMap key、ThreadLocal |
| Phantom 虚 | 回收后入队,无法 get | 堆外内存回收跟踪 |
WeakReference<byte[]> wr = new WeakReference<>(new byte[1024]);
System.gc();
// wr.get() 大概率为 null
4. GC 算法四大思想
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 标记-清除 Mark-Sweep | 两遍扫描 | 简单,不留空 | 碎片、两次遍历 |
| 复制 Copying | Eden + 2 Survivor 8:1:1 | 无碎片,快 | 浪费 Survivor 区 |
| 标记-整理 Mark-Compact | 末段滑动对象 | 无碎片,空间连续 | 移动成本高 |
| 分代 Generational | 弱分代假说:对象朝生夕死 | 综合最优 | 实现复杂 |
调研依据:HotSpot 年轻代用复制,老年代用标记-整理;CMS 是唯一商用标记-清除,ZGC/Shenandoah 改用染色指针+读屏障实现”并发整理”。
5. 七大收集器横向对比
| 收集器 | 代 | 算法 | STW 范围 | 目标延迟 | JDK | 适用堆 |
|---|---|---|---|---|---|---|
| Serial | 新 | 复制 | 全 STW | - | 1.3+ | <100M 客户端 |
| ParNew | 新 | 复制 | 全 STW | - | 1.4+ | CMS 搭档 |
| Parallel Scavenge | 新 | 复制,关注吞吐 | 全 STW | - | 1.4+ | 后台批处理 |
| Serial Old | 老 | 标记-整理 | 全 STW | - | 1.3+ | 客户端兜底 |
| Parallel Old | 老 | 标记-整理 | 全 STW | - | 1.6+ | 吞吐优先 |
| CMS | 老 | 标记-清除(并发) | 初始/重新标记 | <1s | 1.5~14 | 中等堆 |
| G1 | 全 | Region + 增量整理 | 混合阶段 | 百毫秒级 | 1.7+ → 9 默认 | 4G~几十G |
| ZGC | 全 | 染色指针+读屏障 | <1ms | 亚毫秒 | 11+ | TB 级 |
| Shenandoah | 全 | 转发指针+读屏障 | <10ms | 毫秒级 | 12+ | TB 级 |
调研依据:JDK 9 起 G1 为默认,JDK 15 起 ZGC 可生产(JEP 377),JDK 14 CMS 被 deprecated,JDK 21 虚拟线程要求 G1/ZGC 协奏。
6. 选型决策树 + 实战参数
决策顺序:堆 < 4G 用 Parallel(吞吐优先);4~32G 用 G1(平衡);32G+ 追求低延迟用 ZGC;Red Hat 生态偏好 Shenandoah。
# G1 通用生产模板(JDK 11/17 适用)
-Xms8g -Xmx8g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1HeapRegionSize=16m
-XX:+ParallelRefProcEnabled
# 容器环境关键 — 让 JVM 感知 cgroup 限制
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
# 可观测
-Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags:filecount=5,filesize=50m
观测命令:jstat -gcutil <pid> 1s 看各区占比,jcmd <pid> GC.heap_info 看 Region 分布,Async-Profiler 抓 STW 分布。
调研依据:Alibaba Dragonwell 8/11 默认推荐 G1;字节跳动 K8s 容器场景推荐 -XX:MaxRAMPercentage 取代 -Xmx,避免超卖。