使用 jstack 定位线程卡顿的原因
命令:
jstack <pid>,输出所有线程堆栈。重点看线程状态:
BLOCKED、WAITING、TIMED_WAITING。
1 三种关键状态
- BLOCKED:抢锁失败,正在阻塞等待锁(synchronized)
- WAITING :无限等待,没超时。比如
Object.wait()、LockSupport.park() - TIMED_WAITING :带超时的等待,
Thread.sleep()、lock.tryLock(时间)、sleep、RPC/DB等待也经常是这个
业务接口RT(即响应时间)高,但CPU不高,大量线程是上面三种状态,说明线程在等东西,不是CPU忙。
2 BLOCKED:抢synchronized锁卡住
线程状态:BLOCKED。
堆栈样例:
"http-nio-8080-exec-10" #123 prio=5 os_prio=0 tid=0x00007f2b1c12b800 nid=0x234 waiting for monitor entry [0x00007f2af24f9000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.xxx.service.BizService.update(BizService.java:88)
- waiting to lock <0x0000000782104560> (a com.xxx.service.BizService)
解读:
BLOCKED (on object monitor)被synchronized锁堵住waiting to lock <0x0000000782104560>:等待这个对象锁- 找别的线程,看哪个线程
- locked <0x0000000782104560>,就是持有锁的那个线程,看它在干嘛。
大量BLOCKED = 锁竞争激烈。
3 WAITING / TIMED_WAITING 分两类
① 等待线程池队列(正常)
线程状态:WAITING。
"pool-1-thread-1" #20 prio=5 os_prio=0 tid=0x00007f2b20023800 nid=0x293 in Object.wait() [0x00007f2ae97fa000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
看到 LinkedBlockingQueue.take:线程池空闲,在等任务,这是正常现象,不是故障。
② 等待DB/RPC/网络IO(业务阻塞,重点)
线程状态:RUNNABLE。
JDBC等待数据库返回典型栈:
"http-nio-8080-exec-20" #140 prio=5 os_prio=0 tid=0x00007f2b14134000 nid=0x2bc RUNNABLE [0x00007f2ae86f8000]
at java.net.SocketInputStream.socketRead0(Native Method)
at com.mysql.cj.protocol.a.NativeSocketConnection.read(NativeSocketConnection.java:124)
注意:网络IO阻塞时线程状态是 RUNNABLE,不是WAITING!
JVM层面认为线程在跑socket系统调用,栈里出现
socketRead0,代表正在等待数据库网络返回。这个坑很多人踩。
③ 等待获取连接池连接
线程状态:TIMED_WAITING。
拿不到数据库连接,卡在连接池:
"http-nio-8080-exec-30" #150 prio=5 os_prio=0 tid=0x00007f2b14235000 nid=0x2c5 TIMED_WAITING
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java)
at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java)
看到druid/hikari getConnection,大量线程卡在这:数据库连接池不够用。哪怕线程池很大,拿不到DB连接全部卡住。
④ Lock锁(ReentrantLock)等待
线程状态:WAITING。
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java)
parking 代表ReentrantLock等锁。
4 快速排查步骤(实操流程)
-
jstack pid > thread.txt导出堆栈文件 -
统计各类线程数量:
grep 'BLOCKED' thread.txt |wc -l
grep 'WAITING' thread.txt |wc -l
grep 'TIMED_WAITING' thread.txt |wc -l
grep 'socketRead0' thread.txt |wc -l -
大量BLOCKED:找哪个线程hold住monitor锁
-
大量卡在getConnection:连接池max太小
-
大量socketRead0:大量线程等待DB/RPC网络IO
-
如果大量线程卡在队列take:属于空闲线程,不是问题
5 重要避坑
❗网络IO(mysql、rpc)阻塞,线程状态是RUNNABLE ,不是WAITING!
只看状态会漏掉,要看栈方法名,看是不是socketRead0。
6 总结
- BLOCKED → synchronized锁抢不到
- WAITING(parking) → AQS锁ReentrantLock等锁;或线程池空闲等任务
- TIMED_WAITING → sleep、带超时等待
- RUNNABLE + socketRead0 → 等待数据库/网络IO(IO密集最常见)
- 大量卡在getConnection → 连接池耗尽
结合CPU指标:
- CPU高:重点看RUNNABLE状态线程的业务栈,看哪里循环计算;
- CPU低,RT高:看BLOCKED / WAITING / socketRead0 / getConnection。