JVM(Java Virtual Machine)是 Java 程序运行的核心环境。JVM 调优 主要是指通过合理配置 JVM 参数,使应用程序在内存使用、垃圾回收(GC)、吞吐量、响应时间等方面达到最优性能。以下以 Java 8 为核心,做简单介绍:
一、如何正确配置 JVM 参数?
1. 常用参数分类
(1)堆内存相关
-Xms:初始堆大小(建议与 -Xmx 相同,避免动态扩容带来的性能开销)-Xmx:最大堆大小(通常设为物理内存的 50%~70%,但不超过 32G,否则指针压缩失效)-Xmn:年轻代大小(一般为堆的 1/3 ~ 1/4)-XX:MetaspaceSize/-XX:MaxMetaspaceSize:元空间初始和最大大小(Java 8 取代了永久代)
bash
-Xms4g -Xmx4g -Xmn1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
(2)GC 相关(Java 8 默认 Parallel GC)
-XX:+UseSerialGC:单线程 GC(适用于小内存、单核)-XX:+UseParallelGC:并行 GC(高吞吐,默认)-XX:+UseConcMarkSweepGC(CMS):低延迟(已废弃,Java 9+ 移除)-XX:+UseG1GC:G1 GC(兼顾吞吐与延迟,大堆推荐)
bash
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
(3)GC 日志(重要!用于分析)
bash
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:/path/to/gc.log
Java 8 中推荐使用上述参数;Java 9+ 使用统一日志系统(-Xlog:gc*)
(4)其他优化参数
-XX:+UseCompressedOops:启用指针压缩(堆 ≤ 32G 时自动开启,节省内存)-XX:+DisableExplicitGC:禁用System.gc()(防止手动触发 Full GC)-XX:+AlwaysPreTouch:启动时分配所有内存(避免运行时缺页中断)
二、JVM 调优主要指的是什么?
JVM 调优的核心目标是 在有限资源下最大化应用性能,主要包括:
| 调优维度 | 目标 | 关注点 |
|---|---|---|
| 内存调优 | 避免 OOM,减少 GC 频率 | 堆大小、元空间、对象生命周期 |
| GC 调优 | 减少停顿时间 / 提高吞吐量 | 选择合适 GC 算法、调整代大小、GC 参数 |
| CPU 利用率 | 避免 GC 线程争抢 CPU | 并行 GC 线程数(-XX:ParallelGCThreads) |
| 稳定性 | 避免 Full GC、内存泄漏 | 分析 GC 日志、MAT 工具排查 |
关键原则:
- 没有"万能配置",需结合业务场景(高吞吐 vs 低延迟)
- 先监控(GC 日志、JVisualVM、JMC),再调优
- 小步迭代,每次只改一个参数
三、已有调优案例值得学习
案例 1:电商大促系统(高吞吐)
-
问题:促销期间频繁 Full GC,服务卡顿
-
分析:老年代增长过快,Survivor 区过小导致对象过早晋升
-
调优 :
bash-Xms8g -Xmx8g -Xmn3g -XX:SurvivorRatio=8 -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -
结果:Full GC 消失,平均 GC 停顿 < 50ms
案例 2:微服务(低延迟)
-
问题:API 响应时间波动大(偶发 1s+ 停顿)
-
分析:使用默认 Parallel GC,STW 时间长
-
调优 :切换 G1,限制最大停顿
bash-XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=4m -
结果:P99 响应时间稳定在 200ms 内
案例 3:内存泄漏排查
- 现象:堆内存持续增长,最终 OOM
- 工具 :
jmap -dump+ MAT 分析 - 发现:静态 Map 缓存未清理
- 解决 :改用
WeakHashMap或定时清理
推荐学习资源:
- 《深入理解 Java 虚拟机》(周志明)
- Oracle 官方 GC 调优指南(Java 8)
- Alibaba Arthas 实战案例
四、JVM 经典面试题及回答(Java 8)
Q1:说说 JVM 内存结构?
答:Java 8 中 JVM 内存分为:
- 堆(Heap):对象实例存储,分新生代(Eden + S0/S1)和老年代
- 方法区(Metaspace):存储类信息、常量、静态变量(Java 8 用本地内存实现,取代永久代)
- 虚拟机栈:每个线程私有,存储局部变量、方法调用
- 本地方法栈:Native 方法使用
- 程序计数器:记录当前线程执行字节码位置
Q2:CMS 和 G1 的区别?
答:
- CMS:以最短回收停顿为目标,采用"标记-清除",并发执行,但会产生内存碎片,且在 JDK 9 后废弃。
- G1:面向大堆(>4G),将堆划分为 Region,可预测停顿时间,采用"标记-整理"避免碎片,支持并发标记和混合回收。
Q3:什么情况下会触发 Full GC?
答(Java 8):
- 老年代空间不足
- 元空间(Metaspace)不足
- System.gc() 被调用(除非加
-XX:+DisableExplicitGC)- 堆内存担保失败(Minor GC 前,老年代空间不足以容纳 Survivor 晋升对象)
- CMS GC 时并发模式失败(concurrent mode failure)
Q4:如何排查内存泄漏?
答:
- 通过
jstat -gcutil观察老年代是否持续增长- 使用
jmap -dump:format=b,file=heap.hprof <pid>导出堆快照- 用 MAT(Memory Analyzer Tool)分析 dominator tree,找出 GC Roots 引用链
- 常见原因:静态集合缓存、未关闭的连接、监听器未注销等
Q5:为什么建议 -Xms 和 -Xmx 设置相同?
答:避免 JVM 在运行时动态扩展堆内存,减少因内存申请导致的系统调用和页面分配开销,提升性能稳定性。
总结
- JVM 调优 = 监控 + 分析 + 调整
- Java 8 推荐使用 G1 GC(尤其堆 > 4G)
- GC 日志是调优的黄金数据
- 面试重点:内存模型、GC 算法对比、OOM 排查、参数含义