☕ 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:G1HeapRegionSizeRegion大小(1M~32M,2的幂)堆/2048左右大堆用大Region减少管理开销,避免过多Humongous
-XX:InitiatingHeapOccupancyPercent触发Mixed GC的老年代占比35~45调低则更早开始Mixed GC,避免Full GC但增加GC频率
-XX:G1MixedGCLiveThresholdPercentMixed GC回收Region的存活对象阈值85存活率高于此值的Region不回收,节省时间
-XX:ParallelGCThreads并行GC线程数CPU核数过高增加上下文切换,过低回收速度慢
-XX:ConcGCThreads并发GC线程数ParallelGCThreads/4过高抢占业务线程CPU

2.3 ZGC 调优核心参数

⚡ ZGC 关键参数

参数含义推荐值
-XX:+UseZGC启用ZGCJDK 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 排查流程

📋 内存泄漏排查五步法

  1. 确认泄漏:观察 GC 日志,Full GC 后老年代是否持续增长。命令:jstat -gcutil <pid> 1000
  2. 抓取堆Dump
    • 自动:-XX:+HeapDumpOnOutOfMemoryError
    • 手动:jmap -dump:live,format=b,file=heap.hprof <pid>
    • Arthas:heapdump --live /tmp/heap.hprof
  3. 对比分析:抓取两个时间点的Dump,使用 MAT / JProfiler 对比对象数量增长。(MAT → Histogram → Compare Basket)
  4. 定位引用链:对增长最多的对象类,查看 GC Root 最短路径(MAT → Path to GC Roots),找出是谁持有了这些对象。
  5. 修复验证:修复代码后,重新压测并对比 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