JVM性能调优方法

#java #最佳实践 #jvm

总结
  • 调优核心目标:减少 Full GC 频率,降低停顿时间,让对象尽量在新生代回收
  • 调优优先级:先改代码 → 合理分配内存 → 选对 GC 器 → 最后才微调参数
  • 现在基本 G1 起步,大内存上 ZGC;G1 不需要手动设 -Xmn
  • OOM 必备:-XX:+HeapDumpOnOutOfMemoryError,出了问题才有得查

1. 先说结论

JVM 调优的核心目标就一个:减少 Full GC 频率,降低 GC 停顿时间

让对象尽量在新生代回收,别让它们跑到老年代去。

调优优先级:

  1. 先看代码,减少对象创建、修内存泄漏
  2. 合理分配堆内存和分代比例
  3. 选对 GC 器(现在基本上 G1 起步,大内存上 ZGC)
  4. 最后才是参数微调

2. 监控工具

2.1 jstat 看 GC 情况

# 每 1000ms 打印一次,共 10 次
jstat -gc <PID> 1000 10

关键字段:

jstat -gcnew <PID>      # 看新生代,含 TT(晋升年龄)
jstat -gcold <PID>      # 看老年代
jstat -class <PID>      # 看类加载,排查 Metaspace 问题

2.2 jmap 看内存分布

# 看对象分布,按内存大小排序
jmap -histo <PID>

# 生成堆快照,用 MAT 离线分析
jmap -dump:live,format=b,file=dump.hprof <PID>

MAT(Eclipse Memory Analyzer)比 jhat 强很多,排查内存泄漏首选。

2.3 开启 GC 日志

# JDK 8
-XX:+PrintGCDetails -Xloggc:gc.log -XX:+PrintGCDateStamps

# JDK 9+
-Xlog:gc*:file=gc.log:time,uptime

日志里重点看:

3. 内存参数怎么设?

3.1 先估算对象增长速率

示例:支付系统,每台机器 QPS 30
每个请求产生对象约 500 字节
每秒内存占用:30 * 500B ≈ 15KB
考虑其他对象(放大 20 倍):约 300KB/s

3.2 根据估算设参数

4 核 8G 机器,G1 配置示例:

-Xms4G -Xmx4G
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200        # 目标停顿时间,G1 会自动调整
-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs/dump.hprof
-Xlog:gc*:file=/logs/gc.log:time,uptime

G1 不需要手动设 -Xmn,它会自动调整新生代大小。

3.3 压测验证

压测时用 jstat -gc <PID> 1000 盯着看:

4. 频繁 Full GC 的常见原因

4.1 Survivor 区太小,对象提前晋升

# 用 jstat -gcnew 看 TT(实际晋升年龄)
# 如果 TT 很小(比如 1-2),说明对象还没熬到设定年龄就被迫晋升了

# 调大新生代或调整 SurvivorRatio
-XX:SurvivorRatio=4   # Eden:S0:S1 = 4:1:1,Survivor 占比更大

4.2 大对象直接进老年代

// 定时任务一次性捞几百 MB 数据,直接进老年代
List<Data> all = dao.loadAll();

// 改成分批处理
int page = 0;
List<Data> batch;
while (!(batch = dao.loadPage(page++, 1000)).isEmpty()) {
    process(batch);
}

4.3 内存泄漏

老年代持续增长,Full GC 后也降不下来,基本就是泄漏。

常见场景:

// 静态集合一直往里加,不清理
private static final List<Object> CACHE = new ArrayList<>();

// ThreadLocal 用完不 remove
threadLocal.set(obj);
// ... 忘了 threadLocal.remove()

// 监听器注册了不移除
eventBus.register(listener);

排查:jmap -dump 生成快照,MAT 打开,看 "Leak Suspects"。

4.4 代码里调了 System.gc()

# 直接禁掉
-XX:+DisableExplicitGC

4.5 Metaspace 不够

CGLib、动态代理用多了会不断生成新类,撑爆 Metaspace:

-XX:MetaspaceSize=512M
-XX:MaxMetaspaceSize=512M

5. OOM 怎么处理

错误信息 原因 处理
Java heap space 堆内存不够或泄漏 -XX:+HeapDumpOnOutOfMemoryError,MAT 分析
Metaspace 类太多,CGLib 失控 增大 MetaspaceSize,检查动态代理使用
Direct buffer memory NIO 堆外内存泄漏 检查 ByteBuffer 是否及时释放
StackOverflowError 递归太深 检查递归终止条件

OOM 必备启动参数:

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs/dump.hprof

出了 OOM 自动 dump,不然重启之后什么都查不到。

6. 什么时候需要调优?

需要动手的信号:

不用动的情况:

相关链接