☕ JVM调优
JVM调优是Java应用性能优化的核心环节。从内存模型理解对象生命周期,到GC策略选型与参数调优, 再到内存泄漏排查——掌握这些能力就能解决90%以上的JVM性能问题。
1. JVM 内存模型
1.1 运行时数据区
JVM 内存被划分为多个区域,不同区域存储不同类型的数据,也有各自的内存管理和回收策略。
🧠 HotSpot 内存结构
| 区域 | 存储内容 | 线程共享 | GC管理 | OOM场景 |
|---|---|---|---|---|
| 堆(Heap) | 对象实例、数组 | 是 | GC主要管理区域 | 对象过多、内存泄漏 |
| 方法区/元空间 | 类元数据、常量、静态变量 | 是 | 条件回收(Full GC) | 动态加载过多类、CGLIB滥用 |
| 虚拟机栈 | 局部变量表、操作数栈、帧 | 否 | 方法结束自动释放 | 递归过深、线程过多 |
| 本地方法栈 | Native方法调用 | 否 | 同虚拟机栈 | 同虚拟机栈 |
| 程序计数器 | 当前线程字节码行号 | 否 | 无需GC | 无 |
| 直接内存 | NIO DirectByteBuffer | 是 | 非JVM管控,需手动释放 | 未设置-XX:MaxDirectMemorySize |
1.2 堆内存分代结构
分代收集理论是现代GC的基础:绝大多数对象朝生夕死,少数对象长期存活。
📐 堆内存分代模型
┌─────────────────────────────────────────────────────┐ │ Heap │ │ ┌──────────┐ ┌──────────────────────────────────┐ │ │ │ Young │ │ Old (Tenured) │ │ │ │ ┌──────┐ │ │ │ │ │ │ │Eden │ │ │ 长期存活对象 │ │ │ │ │ │ │ │ 大对象(Humongous) │ │ │ │ └──────┘ │ │ │ │ │ │ ┌──┐┌──┐│ │ │ │ │ │ │S0││S1││ └────────────────────────────────────┘ │ │ │ └──┘└──┘│ │ │ └──────────┘ │ │ 对象分配 → Eden → Minor GC → S0/S1 复制 │ │ 存活阈值达标 → 晋升 Old │ └─────────────────────────────────────────────────────┘ 默认比例: Young=1/3, Old=2/3; Eden:S0:S1=8:1:1
2. GC 调优实战
2.1 GC 策略全景对比
⚖️ GC 策略选择矩阵
| GC | 算法 | 适用堆大小 | 停顿目标 | 适用场景 | 核心优势 | 注意事项 |
|---|---|---|---|---|---|---|
| Serial GC | 标记-复制/整理 | < 200MB | 几十ms | 客户端、嵌入式 | 单线程无上下文切换开销 | 多核场景浪费CPU |
| Parallel GC | 标记-复制/整理 | < 4GB | 百ms级 | 批处理、后台任务 | 吞吐量优先,STW并行回收 | Full GC 停顿时间长 |
| G1 GC | 标记-整理+复制 | 4~32GB | 可配置(默认200ms) | Web服务、微服务 | 可预测停顿、Region灵活 | Humongous对象问题 |
| ZGC | 标记-整理+染色指针 | 16MB~16TB | < 1ms | 低延迟、大堆服务 | 超低延迟、并发整理 | 吞吐量略降(~10%) |
| Shenandoah | 并发标记-整理 | 4~32GB | < 10ms | 低延迟(与ZGC类似) | 并发整理、无染色指针 | CPU开销较大 |
💡 JDK版本默认GC
JDK 8: Parallel GC | JDK 9-16: G1 GC | JDK 17+: G1 GC(ZGC 为实验特性,JDK 21 ZGC 稳定)
2.2 G1 GC 调优核心参数
🔧 G1 关键参数及推荐值
| 参数 | 含义 | 推荐值 | 调优依据 |
|---|---|---|---|
-XX:MaxGCPauseMillis | 最大GC停顿目标 | 100~200ms | 业务SLA要求,越小则Young区越小、GC越频繁 |
-XX:G1HeapRegionSize | Region大小(1M~32M,2的幂) | 堆/2048左右 | 大堆用大Region减少管理开销,避免过多Humongous |
-XX:InitiatingHeapOccupancyPercent | 触发Mixed GC的老年代占比 | 35~45 | 调低则更早开始Mixed GC,避免Full GC但增加GC频率 |
-XX:G1MixedGCLiveThresholdPercent | Mixed GC回收Region的存活对象阈值 | 85 | 存活率高于此值的Region不回收,节省时间 |
-XX:ParallelGCThreads | 并行GC线程数 | CPU核数 | 过高增加上下文切换,过低回收速度慢 |
-XX:ConcGCThreads | 并发GC线程数 | ParallelGCThreads/4 | 过高抢占业务线程CPU |
2.3 ZGC 调优核心参数
⚡ ZGC 关键参数
| 参数 | 含义 | 推荐值 |
|---|---|---|
-XX:+UseZGC | 启用ZGC | JDK 17+ 推荐 |
-XX:ZAllocationSpikeTolerance | 分配突增容忍度 | 2.0(默认) |
-XX:ZCollectionInterval | 最大GC间隔(秒) | 0(不限制,默认) |
-XX:ZProactive | 主动GC策略开关 | true(默认) |
3. JVM 参数最佳实践
3.1 通用推荐配置
📋 4C8G 微服务标准模板(JDK 17 + G1)
# 基础配置
-Xms4g -Xmx4g # 堆大小固定,避免动态调整开销
-XX:+UseG1GC # G1垃圾回收器
-XX:MaxGCPauseMillis=200 # 最大GC停顿200ms
-XX:InitiatingHeapOccupancyPercent=40 # 老年代占40%触发Mixed GC
-XX:G1HeapRegionSize=4m # 4M Region
# 元空间
-XX:MaxMetaspaceSize=256m
-XX:MetaspaceSize=128m
# GC日志(JDK 17 统一日志)
-Xlog:gc*:file=/var/log/gc.log:time,level,tags:filesize=100M,filecount=5
# OOM Dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump.hprof
# 直接内存
-XX:MaxDirectMemorySize=512m
# 优化
-XX:+AlwaysPreTouch # 启动预分配物理内存
-XX:-UseBiasedLocking # JDK 15+ 默认关闭偏向锁
-XX:+UseStringDeduplication # 字符串去重(G1)
3.2 大堆场景配置(> 16GB)
📋 32GB 堆 + ZGC 配置
# JDK 21 ZGC 大堆配置
-Xms32g -Xmx32g
-XX:+UseZGC
-XX:ZAllocationSpikeTolerance=5
-XX:SoftMaxHeapSize=28g # 软上限,超过后更积极GC
-XX:+ZGenerational # JDK 21+ 分代ZGC
# 元空间
-XX:MaxMetaspaceSize=512m
-XX:MetaspaceSize=256m
# GC日志
-Xlog:gc*:file=/var/log/gc.log:time,level,tags:filesize=200M,filecount=10
# OOM Dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump.hprof
# 编译优化
-XX:ReservedCodeCacheSize=512m
-XX:+TieredCompilation
4. 内存泄漏排查方法论
🚨 内存泄漏的典型信号
- Full GC 后老年代使用率不下降或持续增长
- 应用运行时间越长,堆内存占用越高,最终OOM
- 每次Full GC回收的内存越来越少
- GC日志中「GC overhead limit exceeded」
4.1 排查流程
📋 内存泄漏排查五步法
- 确认泄漏:观察 GC 日志,Full GC 后老年代是否持续增长。命令:
jstat -gcutil <pid> 1000 - 抓取堆Dump:
- 自动:
-XX:+HeapDumpOnOutOfMemoryError - 手动:
jmap -dump:live,format=b,file=heap.hprof <pid> - Arthas:
heapdump --live /tmp/heap.hprof
- 自动:
- 对比分析:抓取两个时间点的Dump,使用 MAT / JProfiler 对比对象数量增长。(MAT → Histogram → Compare Basket)
- 定位引用链:对增长最多的对象类,查看 GC Root 最短路径(MAT → Path to GC Roots),找出是谁持有了这些对象。
- 修复验证:修复代码后,重新压测并对比 GC 日志,确认老年代稳定。
4.2 常见泄漏模式
🔍 六大内存泄漏模式
| 模式 | 根因 | 表现 | 修复 |
|---|---|---|---|
| ThreadLocal 未清理 | 线程池复用线程,ThreadLocal无法回收 | 线程私有对象持续累积 | finally 中调用 remove() |
| 静态集合累积 | static Map/List 只增不减 | 集合类对象数量不断增长 | 使用 WeakHashMap 或加过期清除机制 |
| 连接未关闭 | 流/连接/Statement 未在 finally 关闭 | Native 内存增长,socket fd 耗尽 | try-with-resources |
| 内部类持有外部引用 | 非静态内部类隐式持有外部类引用 | Activity/Controller 无法GC | 改为 static 内部类 + WeakReference |
| 缓存无界增长 | 无容量限制的本地缓存 | 同静态集合模式 | Caffeine/Guava Cache 设置 maxSize + TTL |
| 监听器未注销 | 注册的Listener未remove | 被监听对象无法GC | 成对管理 register/unregister |
5. GC 日志分析要点
📊 GC 日志关键指标解读
| 指标 | 含义 | 健康标准 |
|---|---|---|
| Minor GC 频率 | Young区回收频率 | 每秒1-5次以内为合理 |
| Minor GC 耗时 | 单次Young GC停顿 | < 50ms(G1) |
| Full GC 频率 | 全局STW回收频率 | 理想为0,每小时<1次可接受 |
| Full GC 耗时 | 单次Full GC停顿 | < 1s(G1),< 100ms(ZGC) |
| 晋升速率 | 对象从Young→Old的速度 | 越慢越好,稳定即可 |
| GC后堆占用 | 每次GC后各代使用量 | GC后有明显下降,且不持续增长 |
| 吞吐量 | GC时间/总运行时间 | > 98%(即GC占比<2%) |
📌 Arthas 快速诊断命令
# GC统计: dashboard / jvm
# 线程分析: thread -n 5 / thread -b(死锁)
# 方法监控: monitor -c 5 com.example.Service method
# 火焰图: profiler start / profiler stop --format html