怎么使用 jstack 定位线程卡顿的原因?

使用 jstack 定位线程卡顿的原因

命令:jstack <pid>,输出所有线程堆栈。

重点看线程状态:BLOCKEDWAITINGTIMED_WAITING

1 三种关键状态

  1. BLOCKED:抢锁失败,正在阻塞等待锁(synchronized)
  2. WAITING :无限等待,没超时。比如 Object.wait()、LockSupport.park()
  3. 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 快速排查步骤(实操流程)

  1. jstack pid > thread.txt 导出堆栈文件

  2. 统计各类线程数量:

    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

  3. 大量BLOCKED:找哪个线程hold住monitor锁

  4. 大量卡在getConnection:连接池max太小

  5. 大量socketRead0:大量线程等待DB/RPC网络IO

  6. 如果大量线程卡在队列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。
相关推荐
羌俊恩3 个月前
Redis 宕机问题一解
java·redis·cgroup·top·kernel·jstack·cpulimit
Tsuki_tl3 个月前
【总结】Java的线程状态
java·后端·面试·多线程·并发编程·线程状态
zfj3218 个月前
java线程的不同状态下调用interrupt()方法后的行为及注意事项
java·开发语言·interrupt·线程中断·线程状态
啊Q老师1 年前
JVM:工具
jstat·jstack·jmap·jps·jvisualvm·jconsole·jhat
码农爱java2 年前
JVM 性能调优 -- JVM常用调优工具【jps、jstack、jmap、jstats 命令】
jvm·jstack·jps·jvm 调优·jmp·jstats
GGBondlctrl2 年前
【后端开发】JavaEE初阶—线程安全问题与加锁原理(超详解)
java·javaee·线程状态·多线程编程安全问题·线程加锁
程序猿进阶2 年前
线程的六种状态
java·开发语言·数据库·缓存·thread·并发编程·线程状态
Jack_hrx2 年前
深入解析Java线程状态与生命周期
java·生命周期·并发编程·线程状态
随心自风流3 年前
双非本科准备秋招(15.1)—— 线程状态
java·求职招聘·秋招·并发编程·线程状态