
JVM问题是Java线上故障的核心重灾区,90%的性能问题最终都会指向JVM。本文提供标准化排查框架+几个可复现的实战案例,所有命令可直接复制执行,从基础监控到深度分析全覆盖,新手也能一步步定位问题。
在应用启动脚本中添加以下参数,故障发生时自动生成所有诊断文件,避免错过最佳排查时机:
# 1. OOM自动生成堆转储(最重要)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump-$(date +%Y%m%d-%H%M%S).hprof
# 2. 输出详细GC日志(分析GC问题必备)
-Xloggc:/var/log/gc-$(date +%Y%m%d-%H%M%S).log
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-XX:+PrintGCApplicationStoppedTime
# 3. 发生OOM时执行告警脚本
-XX:OnOutOfMemoryError="sh /opt/alert.sh %p"
# 4. 开启JFR飞行记录(JDK11+推荐,低开销深度监控)
-XX:+FlightRecorder
-XX:StartFlightRecording=duration=1h,filename=/var/log/recording.jfr工具类型 | 推荐工具 | 适用场景 |
|---|---|---|
JDK原生(零安装) | jps、jstat、jstack、jmap、jinfo | 所有基础排查 |
在线诊断神器 | Arthas(阿里开源) | 生产环境无需重启,一键定位 |
离线分析工具 | HeapHero(在线)、MAT | 堆转储深度分析 |
GC日志分析 | GCEasy(在线) | GC问题快速诊断 |
public class Cpu100Demo {
public static void main(String[] args) {
// 启动2个死循环线程,打满CPU
new Thread(() -> {
while (true) {
// 空循环,CPU空转
}
}, "DeadLoop-Thread-1").start();
new Thread(() -> {
while (true) {
// 复杂计算
Math.pow(Math.random(), Math.random());
}
}, "DeadLoop-Thread-2").start();
}
}定位高CPU进程
top
# 按大写P按CPU降序排序,找到Java进程PID(比如:12345)输出示例:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12345 root 20 0 4.5g 1.2g 12345 S 198.7 15.2 12:34.56 java定位进程内高CPU线程
top -Hp 12345
# 按大写P排序,找到CPU最高的线程TID(比如:12346、12347)输出示例:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12346 root 20 0 4.5g 1.2g 12345 R 99.3 15.2 5:23.45 java
12347 root 20 0 4.5g 1.2g 12345 R 98.7 15.2 5:12.34 java线程ID转换为十六进制 jstack输出的线程ID是十六进制,需要转换:
printf "%x\n" 12346 # 输出:303a
printf "%x\n" 12347 # 输出:303b抓取线程栈定位代码行
jstack 12345 | grep -A 20 -E "303a|303b"输出示例:
"DeadLoop-Thread-1" #1 prio=5 os_prio=0 tid=0x00007f8a9c001000 nid=0x303a runnable [0x00007f8aa0000000]
java.lang.Thread.State: RUNNABLE
at com.example.Cpu100Demo.lambda$main$0(Cpu100Demo.java:7)
at com.example.Cpu100Demo$$Lambda$1/0x0000000800000000.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"DeadLoop-Thread-2" #2 prio=5 os_prio=0 tid=0x00007f8a9c002000 nid=0x303b runnable [0x00007f8a9f000000]
java.lang.Thread.State: RUNNABLE
at com.example.Cpu100Demo.lambda$main$1(Cpu100Demo.java:13)
at com.example.Cpu100Demo$$Lambda$2/0x0000000800000000.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)直接定位到Cpu100Demo.java第7行和第13行的死循环。
import java.util.HashMap;
import java.util.Map;
public class FullGCDemo {
// 静态集合导致内存泄漏
private static final Map<String, byte[]> leakCache = new HashMap<>();
public static void main(String[] args) throws InterruptedException {
// JVM参数:-Xms200m -Xmx200m -Xloggc:gc.log
for (int i = 0; i < 1000000; i++) {
// 每次添加1KB的字节数组,从不清理
leakCache.put("key" + i, new byte[1024]);
Thread.sleep(1);
}
}
}监控GC实时状态(核心命令)
# 每隔1秒输出一次GC统计,持续输出
jstat -gcutil 12345 1000输出示例(异常状态):
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 99.99 99.98 99.99 85.23 82.15 1234 12.345 567 89.012 101.357
0.00 99.99 99.97 99.99 85.23 82.15 1235 12.456 568 89.123 101.579
0.00 99.99 99.99 99.99 85.23 82.15 1236 12.567 569 89.234 101.801异常指标:FGC(Full GC次数)每秒上涨,FGCT(Full GC总耗时)持续增加,老年代使用率O一直保持99%以上。
怎么判断频繁gc:
查看对象统计信息
# 只统计存活对象,按大小排序,取前20行
jmap -histo:live 12345 | head -20输出示例:
num #instances #bytes class name
----------------------------------------------
1: 987654 1012345678 [B
2: 987654 31604928 java.util.HashMap$Node
3: 987654 23703696 java.lang.String发现byte[]和HashMap$Node对象数量异常多,各接近100万个。
导出堆转储深度分析
jmap -dump:live,format=b,file=heapdump.hprof 12345用JVisualVM排查:

上传到HeapHero(https://heaphero.io/)分析,会直接显示:

修复静态集合泄漏:使用完对象后调用remove()
改用带过期时间的缓存:Guava Cache或Caffeine
定期清理缓存:设置最大容量和过期时间
// 正确示例:使用Guava Cache
private static final Cache<String, byte[]> safeCache = CacheBuilder.newBuilder()
.maximumSize(10000) // 最大容量
.expireAfterWrite(30, TimeUnit.MINUTES) // 30分钟过期
.build();java.lang.OutOfMemoryError: Java heap space-Xmx设置的值import java.util.ArrayList;
import java.util.List;
public class HeapOOMDemo {
private static final List<byte[]> list = new ArrayList<>();
public static void main(String[] args) {
// JVM参数:-Xms100m -Xmx100m -XX:+HeapDumpOnOutOfMemoryError
while (true) {
// 每次添加10MB的字节数组
list.add(new byte[10 * 1024 * 1024]);
System.out.println("已添加:" + list.size() + "个对象");
}
}
}确认OOM类型 查看应用错误日志:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.example.HeapOOMDemo.main(HeapOOMDemo.java:9)
Dumping heap to heapdump-20260526-163000.hprof ...
Heap dump file created [104857600 bytes in 0.123 secs]用JVisualVM分析堆转储

也可以用https://heaphero.io/平台直接分析看看:

根因:OOM类的静态list集合持有了大量字节数组。
-Xms4g -Xmx4g(根据业务需求)java.lang.OutOfMemoryError: Metaspaceimport net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
public class MetaspaceOOMDemo {
public static void main(String[] args) {
// JVM参数:-XX:MaxMetaspaceSize=50m
while (true) {
// 动态生成大量类,不缓存
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Object.class);
enhancer.setUseCache(false); // 关键:关闭缓存
enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) ->
proxy.invokeSuper(obj, args1));
enhancer.create();
}
}
}查看元空间使用情况
jstat -gcmetacapacity 12345输出示例:
MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC FGCT GCT
0.0 51200.0 50987.0 0.0 8192.0 7890.0 123 45 12.345 23.456MC(当前元空间大小)已经接近MCMX(最大元空间大小)。
查看类加载统计
jmap -clstats 12345输出会显示已加载类数量异常多(超过10万个)。
-XX:MaxMetaspaceSize=256menhancer.setUseCache(true)工具 | 常用命令 | 作用 |
|---|---|---|
jps | jps -l | 列出所有Java进程PID和主类名 |
jstat | jstat -gcutil <PID> 1000 | 实时监控GC情况 |
jstack | jstack <PID> > threaddump.txt | 抓取线程栈 |
jmap | jmap -histo:live <PID> | 查看对象统计 |
jmap | jmap -dump:live,format=b,file=heap.hprof <PID> | 导出堆转储 |
jinfo | jinfo -flags <PID> | 查看JVM启动参数 |
命令 | 作用 |
|---|---|
dashboard | 实时查看CPU、内存、线程、GC |
thread -n 3 | 查看最耗CPU的3个线程 |
thread -b | 查看阻塞其他线程的死锁线程 |
heapdump /tmp/heap.hprof | 导出堆转储 |
jmap -histo | 查看对象统计 |
查看占用cpu最高的进程
top
查看进程的线程
top -Hp pid
打印线程id对应的16进制
printf "%x\n" tid
查看堆栈信息,tid是16进制的
jstack pid | grep tid -A 30
检查gc是否频繁
jstat -gcutil pid 1000
导出堆栈文件
jmap dump:format=b,file=heap.hprof PID
使用MAT、jvisualvm、heaphero网站等查看堆栈文件