知识模块
☕ Java 知识模块
十二、性能优化
JVM 调优

JVM 调优

面试高频考点:JVM 参数分类、堆内存调优、GC 调优策略、常见问题排查工具

一、JVM 参数分类

JVM 参数分为三类:

1. 标准参数(-)

所有 JVM 版本都支持,稳定不变。

java -version           # 查看 JVM 版本
java -help              # 查看帮助
java -showversion       # 显示版本后继续执行
java -cp /path/to/app   # 设置类路径
java -Dproperty=value   # 设置系统属性

2. 非标准参数(-X)

部分 JVM 支持,可能随版本变化。

# 内存相关
-Xms512m       # 初始堆大小
-Xmx2g         # 最大堆大小
-Xmn256m       # 新生代大小
-Xss256k       # 线程栈大小
 
# JIT 相关
-Xint          # 仅解释执行
-Xcomp         # 优先编译执行
-Xmixed        # 混合模式(默认)
 
# 其他
-Xnoclassgc    # 禁用类垃圾回收
-Xdiag         # 显示诊断信息

3. 不稳定参数(-XX)

高级参数,可能随时变化,用于调优。

# 布尔类型参数
-XX:+UseG1GC           # 启用 G1 收集器
-XX:+PrintGCDetails    # 打印 GC 详情
-XX:-UseCompressedOops # 关闭压缩指针
 
# 数值类型参数
-XX:NewRatio=2         # 新生代:老年代 = 1:2
-XX:SurvivorRatio=8    # Eden:Survivor = 8:1
-XX:MaxTenuringThreshold=15  # 晋升年龄阈值
 
# 字符串类型参数
-XX:HeapDumpPath=/logs/heap.hprof  # Heap Dump 路径

二、内存参数配置

堆内存参数

┌──────────────────────────────────────────────────────────────┐
│                        JVM 内存结构                           │
├──────────────────────────────────────────────────────────────┤
│  堆内存 (-Xms, -Xmx)                                         │
│  ┌─────────────────────────┬────────────────────────────┐   │
│  │      新生代 (-Xmn)       │         老年代              │   │
│  │  ┌─────────┬─────────┐  │                            │   │
│  │  │  Eden   │ Survivor │  │                            │   │
│  │  │  8:1:1  │  S0  S1 │  │                            │   │
│  │  └─────────┴─────────┘  │                            │   │
│  └─────────────────────────┴────────────────────────────┘   │
├──────────────────────────────────────────────────────────────┤
│  方法区/元空间 (-XX:MetaspaceSize, -XX:MaxMetaspaceSize)     │
├──────────────────────────────────────────────────────────────┤
│  线程栈 (-Xss)                                               │
├──────────────────────────────────────────────────────────────┤
│  直接内存 (-XX:MaxDirectMemorySize)                          │
└──────────────────────────────────────────────────────────────┘

关键参数详解

# 堆内存配置
-Xms2g                  # 初始堆大小,建议与 -Xmx 相同
-Xmx2g                  # 最大堆大小,一般设为物理内存的 50%-80%
-Xmn1g                  # 新生代大小,或用 -XX:NewRatio
-XX:NewRatio=2          # 新生代:老年代 = 1:2
-XX:SurvivorRatio=8     # Eden:S0:S1 = 8:1:1
 
# 元空间配置(JDK 8+)
-XX:MetaspaceSize=128m       # 初始元空间大小
-XX:MaxMetaspaceSize=256m    # 最大元空间大小
 
# 线程栈
-Xss256k               # 每个线程栈大小,默认 1M
 
# 直接内存
-XX:MaxDirectMemorySize=512m  # NIO 直接内存上限

内存配置原则

# 4GB 物理内存服务器推荐配置
-Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
 
# 8GB 物理内存服务器推荐配置
-Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
 
# 16GB 物理内存服务器推荐配置
-Xms8g -Xmx8g -Xmn4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
 
# 配置原则:
# 1. -Xms 和 -Xmx 设为相同,避免动态扩容开销
# 2. 堆内存不超过物理内存的 80%,留给 OS 和其他进程
# 3. 新生代一般占堆的 1/3 到 1/2
# 4. 元空间根据类数量调整,一般 128M-512M 足够

三、GC 参数配置

Serial GC(串行收集器)

# 适用场景:单核 CPU、小内存应用、客户端应用
-XX:+UseSerialGC
 
# 参数
-XX:SurvivorRatio=8
-XX:PretenureSizeThreshold=3145728  # 大于 3MB 对象直接进老年代

Parallel GC(并行收集器,JDK 8 默认)

# 适用场景:吞吐量优先、后台计算任务
-XX:+UseParallelGC
-XX:+UseParallelOldGC
 
# 参数
-XX:MaxGCPauseMillis=200    # 最大 GC 暂停时间目标
-XX:GCTimeRatio=99          # 吞吐量目标 = 1/(1+99) = 1%
-XX:ParallelGCThreads=4     # GC 线程数,默认 CPU 核数
-XX:+UseAdaptiveSizePolicy  # 自适应调节(默认开启)

CMS GC(并发标记清除)

# 适用场景:低延迟、响应快、Web 服务
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC           # 新生代用 ParNew
 
# 参数
-XX:CMSInitiatingOccupancyFraction=75  # 老年代占用 75% 触发 CMS
-XX:+UseCMSCompactAtFullCollection     # Full GC 时压缩
-XX:CMSFullGCsBeforeCompaction=0       # 每次 Full GC 都压缩
-XX:ConcGCThreads=4                    # 并发 GC 线程数
 
# 注意:JDK 14 已移除 CMS

G1 GC(JDK 9+ 默认)

# 适用场景:大堆内存(>4GB)、低延迟、服务端应用
-XX:+UseG1GC
 
# 参数
-XX:MaxGCPauseMillis=200           # 目标暂停时间(默认 200ms)
-XX:G1HeapRegionSize=4m            # Region 大小(1-32MB)
-XX:InitiatingHeapOccupancyPercent=45  # 堆占用 45% 触发并发标记
-XX:G1NewSizePercent=5             # 新生代最小比例
-XX:G1MaxNewSizePercent=60         # 新生代最大比例
-XX:G1ReservePercent=10            # 预留空间防止晋升失败
 
# 大对象处理
-XX:G1HeapRegionSize=16m           # 增大 Region 减少大对象

ZGC(JDK 15+)

# 适用场景:超大堆(TB 级)、极低延迟(<10ms)
-XX:+UseZGC
 
# 参数
-XX:ZCollectionInterval=5     # GC 间隔(秒)
-XX:ZAllocationSpikeTolerance=2  # 分配尖峰容忍度
-XX:+UnlockDiagnosticVMOptions  # 解锁诊断选项
-XX:+ZProactive                # 主动 GC
 
# JDK 21+ 分代 ZGC
-XX:+UseZGC -XX:+ZGenerational

Shenandoah GC(JDK 12+)

# 适用场景:低延迟、大堆内存
-XX:+UseShenandoahGC
 
# 参数
-XX:ShenandoahGCHeuristics=compact  # GC 策略

四、GC 日志配置

JDK 8 及之前

# 基础 GC 日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/logs/gc.log
 
# 详细 GC 日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintHeapAtGC
-XX:+PrintTenuringDistribution
-XX:+PrintTLAB
-Xloggc:/logs/gc.log
 
# GC 日志滚动(JDK 8u40+)
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=10M

JDK 9 及之后

# 基础 GC 日志
-Xlog:gc*:file=/logs/gc.log:time,level
 
# 详细 GC 日志
-Xlog:gc*=info:file=/logs/gc.log:time,level,tags
 
# 完整诊断日志
-Xlog:gc*=info:file=/logs/gc.log:time,level,tags:filecount=10,filesize=10M
 
# 分项配置
-Xlog:gc,gc+heap=debug:file=/logs/gc.log:time

日志分析示例

# G1 Young GC 日志
[2024-01-15T10:00:00.123+0800][info ][gc,start     ] GC(0) Pause Young (G1 Evacuation Pause)
[2024-01-15T10:00:00.125+0800][info ][gc,heap      ] GC(0) Eden regions: 24->0(20)
[2024-01-15T10:00:00.125+0800][info ][gc,heap      ] GC(0) Survivor regions: 0->3(3)
[2024-01-15T10:00:00.125+0800][info ][gc,heap      ] GC(0) Old regions: 0->5
[2024-01-15T10:00:00.125+0800][info ][gc,metaspace ] GC(0) Metaspace: 10240K->10240K(1056768K)
[2024-01-15T10:00:00.125+0800][info ][gc           ] GC(0) Pause Young (G1 Evacuation Pause) 24M->8M(256M) 2.123ms

# 解读:
# - Eden 从 24 个 Region 变为 0(全部回收)
# - Survivor 从 0 变为 3(晋升/存活)
# - 老年代从 0 变为 5(晋升)
# - 堆从 24M 降到 8M
# - GC 耗时 2.123ms

五、常见问题排查

1. OOM 问题排查

java.lang.OutOfMemoryError: Java heap space

# 自动 Heap Dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs/heap.hprof
 
# 分析工具
# 1. jvisualvm
# 2. MAT (Memory Analyzer Tool)
# 3. JProfiler
# 4. YourKit

java.lang.OutOfMemoryError: Metaspace

# 原因:类加载过多、动态代理、反射
# 解决:增大元空间
-XX:MaxMetaspaceSize=512m
 
# 查看类加载情况
jcmd <pid> VM.classloader_stats
jmap -clstats <pid>

java.lang.OutOfMemoryError: GC overhead limit exceeded

# 原因:GC 时间过长,效率低下
# 解决:优化对象创建、增大堆内存、检查内存泄漏
-XX:-UseGCOverheadLimit  # 禁用该限制(不推荐)

2. 内存泄漏排查

# 1. 查看 JVM 内存使用
jstat -gc <pid> 1000  # 每秒打印一次 GC 情况
 
# 2. 查看堆内存对象统计
jmap -histo <pid> | head -20
 
# 3. 导出 Heap Dump
jmap -dump:live,format=b,file=heap.hprof <pid>
 
# 4. 分析 Heap Dump
# 使用 MAT 或 JVisualVM 分析对象引用链

3. CPU 飙高排查

# 1. 找到高 CPU 进程
top -H -p <pid>  # 显示进程的线程
 
# 2. 将线程 ID 转为 16 进制
printf "%x\n" <thread_id>
 
# 3. 查看线程堆栈
jstack <pid> | grep -A 20 <hex_thread_id>
 
# 4. 或使用 jcmd
jcmd <pid> Thread.print

4. GC 频繁问题

# 查看 GC 统计
jstat -gcutil <pid> 1000 10  # 每秒打印 10 次
 
# 输出示例
  S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
 512.0  512.0   0.0    0.0   8192.0   2048.0    16384.0    8192.0   10240.0  8192.0 1024.0  896.0     10    0.100   2      0.200    0.300
 
# 关注指标
# - YGC: Young GC 次数
# - YGCT: Young GC 总时间
# - FGC: Full GC 次数
# - FGCT: Full GC 总时间
# - OU: 老年代使用量

六、调优实战案例

案例 1:Web 服务响应慢

现象:Web 服务偶尔响应慢,日志无异常

排查步骤

# 1. 查看 GC 情况
jstat -gcutil <pid> 1000
 
# 发现 FGC 每分钟 1-2 次,每次 500ms+
 
# 2. 查看堆内存使用
jmap -heap <pid>
 
# 发现老年代使用率 90%+
 
# 3. 分析 GC 日志
# 发现对象快速晋升到老年代

解决方案

# 原配置
-Xms2g -Xmx2g -Xmn512m
 
# 优化后
-Xms4g -Xmx4g -Xmn2g           # 增大堆和新生代
-XX:+UseG1GC                    # 切换到 G1
-XX:MaxGCPauseMillis=100        # 设置暂停目标

案例 2:批量任务 OOM

现象:批量处理任务时 OOM

排查步骤

# 1. 启用 Heap Dump
-XX:+HeapDumpOnOutOfMemoryError
 
# 2. 分析 Dump 文件
# 发现大量 ArrayList 对象,每个 100MB+

解决方案

// 问题代码:一次性加载所有数据
List<Data> allData = dataService.loadAll();  // 几百万条数据
 
// 优化方案:分批处理
int batchSize = 1000;
int offset = 0;
while (true) {
    List<Data> batch = dataService.loadBatch(offset, batchSize);
    if (batch.isEmpty()) break;
    process(batch);
    offset += batchSize;
}

案例 3:启动慢优化

现象:应用启动耗时 2 分钟

排查步骤

# 1. 添加启动日志
-XX:+PrintFlagsFinal -XX:+PrintGCDetails
 
# 2. 分析启动日志
# 发现大量类加载和编译

解决方案

# 启用类数据共享
java -Xshare:dump  # 生成共享归档
-Xshare:on         # 使用共享归档
 
# 启用分层编译优化
-XX:+TieredCompilation
-XX:TieredStopAtLevel=1  # 启动时只 C1 编译
 
# 延迟 JIT 编译
-XX:+BackgroundCompilation

七、常用工具

命令行工具

工具用途
jps查看 Java 进程
jstat查看 JVM 统计信息
jinfo查看/修改 JVM 参数
jmap内存映射、Heap Dump
jstack线程堆栈快照
jcmd多功能诊断工具
# jps:查看 Java 进程
jps -lvm
 
# jstat:GC 统计
jstat -gc <pid> 1000
jstat -gcutil <pid>
jstat -gccause <pid>
 
# jinfo:查看参数
jinfo -flags <pid>
jinfo -flag UseG1GC <pid>
 
# jmap:堆内存分析
jmap -heap <pid>
jmap -histo <pid> | head -20
jmap -dump:format=b,file=heap.hprof <pid>
 
# jstack:线程分析
jstack -l <pid>
jstack -l <pid> > thread.txt
 
# jcmd:多功能
jcmd <pid> help
jcmd <pid> VM.flags
jcmd <pid> GC.heap_info
jcmd <pid> Thread.print
jcmd <pid> GC.heap_dump /logs/heap.hprof

可视化工具

工具用途
JConsole监控 JVM
JVisualVM监控、分析、Dump
MAT内存分析
JProfiler商业性能分析
Arthas阿里开源诊断工具
GCViewerGC 日志分析

GC 日志分析网站

八、面试要点

Q1: 生产环境如何配置 JVM 参数?

回答要点

# 核心原则
1. -Xms  -Xmx 设为相同,避免动态扩容
2. 堆内存不超过物理内存 80%
3. 新生代占堆 1/3  1/2
4. 开启 GC 日志用于问题排查
5. 配置 OOM 时自动 Heap Dump
 
# 推荐配置(8GB 内存服务器)
-Xms4g -Xmx4g -Xmn2g 
-XX:+UseG1GC 
-XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs/heap.hprof
-Xlog:gc*:file=/logs/gc.log:time,level,tags:filecount=10,filesize=10M

Q2: 如何选择垃圾收集器?

回答要点

场景推荐收集器
小内存/客户端Serial
吞吐量优先Parallel
低延迟/Web 服务G1
超大堆/极低延迟ZGC

Q3: OOM 如何排查?

回答要点

  1. 查看 OOM 类型(Heap/Metaspace/Stack)
  2. 配置 -XX:+HeapDumpOnOutOfMemoryError
  3. 使用 MAT 分析 Heap Dump
  4. 查看大对象和引用链
  5. 定位内存泄漏或容量不足

Q4: CPU 飙高如何排查?

回答要点

  1. top -H -p <pid> 找到高 CPU 线程
  2. printf "%x\n" <tid> 转为 16 进制
  3. jstack <pid> | grep <hex_tid> 查看堆栈
  4. 分析是否死循环、频繁 GC、锁竞争

Q5: Young GC 和 Full GC 触发条件?

回答要点

  • Young GC:Eden 区满
  • Full GC:
    • 老年代空间不足
    • 方法区/元空间满
    • System.gc() 调用
    • CMS GC 时 Promotion Failed
    • G1 的 Allocation Failed

小结

  • JVM 参数分三类:标准(-)、非标准(-X)、不稳定(-XX)
  • 内存配置:-Xms=-Xmx,新生代占 1/3-1/2
  • GC 收集器选择:G1 是主流,ZGC 是趋势
  • 问题排查:jstat/jmap/jstack/jcmd 是必备技能
  • 开启 GC 日志和 Heap Dump,便于问题定位