在Java应用运行过程中,线程阻塞、死锁等问题会直接导致服务响应变慢甚至不可用,jstack是JDK自带的线程堆栈分析工具,导出的线程Dump文件能够完整记录当前JVM中所有线程的运行状态、持有锁和等待锁的信息,是排查这类问题的核心依据。其中BLOCKED和WAITING是两种常见的阻塞状态,很多开发者容易混淆两者的区别,也不清楚如何从Dump文件中定位死锁根源。

线程Dump文件基础结构
执行jstack -l 进程ID命令即可导出线程Dump文件,文件内容按线程为单位分段展示,每个线程段包含以下核心信息:
- 线程名称、优先级、线程ID、本地ID
- 线程当前状态,比如BLOCKED、WAITING、RUNNABLE等
- 线程调用堆栈,展示方法调用的层级关系
- 锁相关信息,包括持有的锁、等待的锁、锁的地址和所属类
以下是一个简化的线程Dump片段示例:
"thread-1" prio=5 tid=0x000000001d8b9000 nid=0x2d34 waiting for monitor entry [0x000000001f7ff000] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.DeadLockService.methodA(DeadLockService.java:20) - waiting to lock <0x00000000d5c8a2b8> (a java.lang.Object) - locked <0x00000000d5c8a2c8> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748)
BLOCKED与WAITING状态的区别
BLOCKED状态
线程处于BLOCKED状态时,是在等待进入synchronized修饰的同步代码块或方法,因为对应的对象锁已经被其他线程持有,所以当前线程被阻塞。BLOCKED状态的线程不会自动唤醒,只有持有锁的线程释放锁之后,才会参与锁的竞争。
在Dump文件中,BLOCKED状态的线程会明确标注waiting for monitor entry,同时会列出waiting to lock的锁对象信息。
WAITING状态
线程处于WAITING状态时,是调用了Object.wait()、Thread.join()、LockSupport.park()等方法,主动放弃了CPU执行权,等待其他线程的唤醒。WAITING状态的线程不会占用CPU资源,直到其他线程调用对应的notify()、notifyAll()或者中断该线程,才会重新进入就绪状态。
在Dump文件中,WAITING状态的线程会标注waiting on condition,同时会列出等待的条件或者关联的锁对象。
死锁的形成与特征
死锁是指两个或两个以上的线程互相持有对方需要的锁,并且都在等待对方释放锁,导致所有相关线程都无法继续执行的场景。死锁中的线程通常会处于BLOCKED状态,因为都在等待对方持有的synchronized锁。
死锁的核心特征:
- 至少两个线程互相持有对方等待的锁
- 所有死锁线程都处于BLOCKED状态
- 线程的等待锁和持有锁形成闭环
死锁排查完整步骤
步骤1:导出线程Dump文件
首先通过jps命令获取目标Java进程的ID,然后执行jstack命令导出Dump文件:
# 查看Java进程ID jps # 导出线程Dump,保存到文件 jstack -l 12345 > thread_dump.log
步骤2:定位死锁线程
打开Dump文件后,可以直接搜索deadlock关键词,jstack会在文件末尾自动输出检测到的死锁信息,如下所示:
Found one Java-level deadlock: ============================= "thread-2": waiting to lock monitor 0x000000001d8b9c58 (object 0x00000000d5c8a2b8, a java.lang.Object), which is held by "thread-1" "thread-1": waiting to lock monitor 0x000000001d8b9d68 (object 0x00000000d5c8a2c8, a java.lang.Object), which is held by "thread-2" Java stack information for the threads listed above: =================================================== "thread-2": at com.example.DeadLockService.methodB(DeadLockService.java:35) - waiting to lock <0x00000000d5c8a2b8> (a java.lang.Object) - locked <0x00000000d5c8a2c8> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748) "thread-1": at com.example.DeadLockService.methodA(DeadLockService.java:20) - waiting to lock <0x00000000d5c8a2c8> (a java.lang.Object) - locked <0x00000000d5c8a2b8> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.
步骤3:分析死锁原因
从上面的死锁信息可以看到,thread-1持有锁0x00000000d5c8a2b8,等待锁0x00000000d5c8a2c8;thread-2持有锁0x00000000d5c8a2c8,等待锁0x00000000d5c8a2b8,两者形成闭环,导致死锁。
结合调用堆栈可以找到对应的业务代码,比如上面的死锁出现在DeadLockService的methodA和methodB方法中,通常是这两个方法获取锁的顺序不一致导致的。
步骤4:修复死锁问题
解决死锁的核心是统一多个线程获取锁的顺序,比如所有线程都先获取锁A再获取锁B,就可以避免闭环。也可以通过使用超时锁、减少锁的持有范围等方式降低死锁发生的概率。
WAITING状态相关阻塞排查
WAITING状态本身不会形成死锁,但如果调用wait()方法的线程没有被其他线程唤醒,也会导致线程一直阻塞。排查时可以查看WAITING线程的调用堆栈,确认对应的唤醒逻辑是否正常,是否存在唤醒线程提前退出、没有执行notify()的情况。
以下是一个WAITING状态的Dump片段:
"wait-thread" prio=5 tid=0x000000001d8ba000 nid=0x2e10 waiting on condition [0x000000001f8ff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000d5c8a3d8> (a java.lang.Object) at com.example.WaitService.doWait(WaitService.java:15) - locked <0x00000000d5c8a3d8> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748)
这段信息表示该线程在WaitService.doWait方法中调用了Object.wait(),正在等待其他线程唤醒0x00000000d5c8a3d8这个对象锁。
注意事项
- 线程Dump是某一时刻的快照,如果问题偶发,建议多次导出Dump文件对比分析
- 除了BLOCKED和WAITING,还有TIMED_WAITING状态,是带超时的等待,排查思路和WAITING类似
- 如果是使用
java.util.concurrent包下的锁(比如ReentrantLock)导致的阻塞,线程状态可能是WAITING或者TIMED_WAITING,锁信息会显示为parking to wait for,排查逻辑和synchronized锁类似