线上问题排查黄金组合:jstack + top + jstat综合使用指南

发布时间:2025-12-24 15:10  浏览量:3

掌握这三把利剑,5分钟定位线上Java问题

场景再现

:凌晨2点,报警响起——CPU飙升到95%,响应时间从50ms暴增到5秒。你是值班工程师,怎么办?

单一工具只能看到冰山一角:

top能看到CPU高,但不知道哪个Java线程在作祟jstack能看到线程堆栈,但不知道哪个最耗CPUjstat能看GC,但不知道GC是否影响线程

黄金组合 = top + jstack + jstat

,让你快速形成排查闭环。

# 1. 找到最耗CPU的Java进程top -c# 输出示例:# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND# 12345 appuser 20 0 12.3g 4.2g 120m S 95.3 13.4 30:20.34 java -Xmx8g -jar app.jar# 2. 查看该进程内的线程CPU占用top -Hp 12345# 输出示例:# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND# 12346 appuser 20 0 12.3g 4.2g 120m R 87.5 13.4 5:20.15 java# 12347 appuser 20 0 12.3g 4.2g 120m S 0.3 13.4 0:01.23 java

关键点

:记录高CPU线程的PID(如12346),将其转为16进制:

# 抓取进程的线程堆栈jstack -l 12345 > /tmp/jstack.$(date +%s).log# 或者使用更高效的方式,直接查找问题线程jstack 12345 | grep -A 10 -B 5 "nid=0x303a"

grep结果示例

"http-nio-8080-exec-1" #32 daemon prio=5 os_prio=0 tid=0x00007f8b1c0e8000 nid=0x303a runnable [0x00007f8b0c7f7000]java.lang.Thread.State: RUNNABLEat java.util.regex.Pattern$Loop.match(Pattern.java:4780)at java.util.regex.Pattern$GroupTail.match(Pattern.java:4717)at java.util.regex.Pattern$BranchConn.match(Pattern.java:4568)at java.util.regex.Pattern$CharProperty.match(Pattern.java:3777)at java.util.regex.Pattern$Branch.match(Pattern.java:4606)at java.util.regex.Pattern$GroupHead.match(Pattern.java:4658)at java.util.regex.Pattern.match(Pattern.java:1134)at java.util.regex.Matcher.match(Matcher.java:637)

一眼看出问题

:正则表达式陷入循环!这就是CPU 87.5%的元凶。

# 每隔1秒收集一次GC数据,共收集10次jstat -gcutil 12345 1000 10# 输出示例:# S0 S1 E O M CCS YGC YGCT FGC FGCT GCT# 0.00 96.88 23.21 85.91 94.23 92.45 320 8.918 5 1.236 10.154# 0.00 96.88 45.67 85.91 94.23 92.45 320 8.918 5 1.236 10.154# 0.00 96.88 68.92 85.91 94.23 92.45 320 8.918 5 1.236 10.154

关键指标解读

O(Old区使用率):85.91% → 老年代快满了FGC:Full GC次数5次,不算多GCT:总GC时间10.154秒

结论

:当前CPU问题不是GC引起的

案例1:死循环导致CPU 100%

# top发现线程0x4cd2占用98% CPU# jstack找到该线程:"Thread-0" #1 prio=5 tid=0x00007fb00c809800 nid=0x4cd2 runnablejava.lang.Thread.State: RUNNABLEat com.example.BadService.infiniteLoop(BadService.java:25)at com.example.BadService.run(BadService.java:18)# BadService.java关键代码:public void infiniteLoop {while (true) { // 致命错误!// 业务逻辑}}

案例2:锁竞争导致线程BLOCKED

"pool-1-thread-2" #12 prio=5 tid=0x00007f8b1c123000 nid=0x4ce3 waiting for monitor entryjava.lang.Thread.State: BLOCKED (on object monitor)at com.example.OrderService.process(OrderService.java:45)- waiting to lock (a com.example.OrderService)"pool-1-thread-1" #11 prio=5 tid=0x00007f8b1c121800 nid=0x4ce2 runnablejava.lang.Thread.State: RUNNABLEat com.example.OrderService.process(OrderService.java:50)- locked (a com.example.OrderService)

创建 check_java_problem.sh:

#!/bin/bashPID=$1echo "=== 1. 检查进程 $PID 的CPU占用 ==="top -b -n1 -p $PID | tail -2echo -e "\n=== 2. 检查线程CPU占用(前5)==="top -H -b -n1 -p $PID | head -12 | tail -6echo -e "\n=== 3. 检查GC状态 ==="jstat -gcutil $PID 1000 3echo -e "\n=== 4. 抓取jstack(保存到/tmp)==="jstack -l $PID > /tmp/jstack_${PID}_$(date +%H%M%S).logecho "堆栈已保存: /tmp/jstack_${PID}_*.log"# 自动转换线程IDHIGH_THREAD=$(top -H -b -n1 -p $PID | grep java | head -1 | awk '{print $1}')if [ ! -z "$HIGH_THREAD" ]; thenHEX_ID=$(printf "%x" $HIGH_THREAD)echo -e "\n=== 5. 分析最高CPU线程 (nid=0x$HEX_ID) ==="jstack $PID | grep -A 10 -B 5 "nid=0x$HEX_ID"fi

使用方式:./check_java_problem.sh 12345

# jstat发现规律# 时间点1: O=70%, FGC=10# 时间点2: O=95%, FGC=11 # Full GC发生# 时间点3: O=30%, FGC=11 # Full GC完成

可能原因

:内存泄漏

解决方法

:jmap生成heapdump,用MAT分析java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for 四、避免常见陷阱

黄金组合不是万能钥匙,但能解决80%的线上问题。真正的专家不是记住所有命令,而是:

理解原理

:知道每个状态(RUNNABLE、BLOCKED、WAITING)的意义

形成直觉

:看到现象就能猜测可能原因

快速验证

:用最少的命令验证猜测

持续学习

:新问题总是会出现