标准线上 JVM 问题排查流程(完整版)
1. 定位问题进程
-
top看系统负载、CPU、内存占用最高的进程 -
jps -l精确拿到 Java 进程 PID2. 快速判断问题类型
-
CPU 飙高
-
内存溢出 / GC 频繁 / OOM
-
线程死锁、阻塞、夯死
-
接口慢、响应超时
CPU 占用过高
top -Hp <pid>找到耗 CPU 的线程 PID- 转 16 进制:
printf "%x\n" <tid> jstack <pid> | grep <16进制tid> -A 20定位到具体代码行、死循环、频繁 GC、频繁锁竞争。
场景:Java 服务 CPU 飙到 300%
你运行:
top
看到:
PID USER %CPU COMMAND
12345 app 300 java
👉 确认是 Java 进程 12345 占用高 CPU
🔍 第一步:找"哪个线程"在烧 CPU
top -Hp 12345
输出类似:
PID USER %CPU COMMAND
12345 app 300 java
12380 app 98 java
12381 app 97 java
12382 app 0.3 java
👉 重点看高 CPU 的线程:
1238012381
🔢 第二步:线程 ID 转 16 进制
printf "%x\n" 12380
得到:
305c
再转另一个:
printf "%x\n" 12381
305d
🧵 第三步:用 jstack 定位代码
jstack 12345 | grep 305c -A 20
你会看到类似:
"pool-3-thread-1" #45 prio=5 os_prio=0 tid=0x00007f... nid=0x305c runnable
java.lang.Thread.State: RUNNABLE
at com.example.demo.TestService.loop(TestService.java:42)
at com.example.demo.TestService.run(TestService.java:30)
🎯 结论:问题代码
public void loop() {
while (true) {
// 没有任何阻塞、sleep、IO
}
}
👉 这是典型的:
❌ 死循环导致 CPU 100%
🧠 再给你几个"真实常见案例"
⚠️ 案例 1:死循环(最常见)
while (true) {
if (queue.isEmpty()) {
continue; // ❌ 空转
}
}
👉 现象:
- CPU 飙高
- jstack 显示 RUNNABLE
- 一直停在同一行
⚠️ 案例 2:频繁 GC
jstack 可能看到:
"GC task thread#0"
或者:
java.lang.Thread.State: RUNNABLE
at java.util.Arrays.copyOf(...)
👉 特征:
- CPU 高
- GC 日志频繁
👉 验证:
jstat -gcutil 12345 1000
⚠️ 案例 3:锁竞争(自旋)
java.lang.Thread.State: RUNNABLE
at java.util.concurrent.locks.ReentrantLock$NonfairSync.tryAcquire
👉 说明:
- 多线程抢锁
- 没阻塞,疯狂自旋 → CPU 高
⚠️ 案例 4:JSON / 加密 / 正则性能问题
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString
或:
at java.util.regex.Pattern.match
👉 特点:
- CPU 高
- 不一定是死循环
- 是"计算密集型热点"
🔥 一句话总结这套流程
👉 本质就是:
进程 → 线程 → 线程栈 → 代码行
具体步骤:
top→ 找进程top -Hp→ 找线程printf→ 转 16 进制jstack→ 找代码
✅ 实战小技巧(很重要)
- 多抓几次
jstack(间隔 1-2 秒)
jstack 12345 > dump1.txt
sleep 2
jstack 12345 > dump2.txt
👉 如果一直卡在同一行 → 基本就是问题点