BLOCKED状态表明线程正等待获取synchronized锁,关键需定位“等待哪把锁”及“谁持有该锁”;jstack中通过waiting to lock地址反查持锁线程,Arthas的thread -b可自动检测死锁,若持锁线程为RUNNABLE但不释放,则可能卡在JNI、同步I/O或OS层。

线程BLOCKED状态意味着它在等synchronized锁
看到大量线程处于java.lang.Thread.State: BLOCKED (on object monitor),基本可以断定:这些线程正卡在进入某个synchronized代码块或方法的入口处,因为目标对象的monitor lock被别的线程长期持有。这不是死循环,也不是I/O阻塞,而是典型的锁争用问题。
关键不是“为什么BLOCKED”,而是“它在等哪个锁?谁占着不放?”——这两点必须同时定位。
jstack里看BLOCKED线程的locked/waiting to lock字段
执行jstack -l <pid></pid>后,在输出中找类似这样的片段:
Thread-1"#12" prio=5 os_prio=0 tid=0x00007f48740eb800 nid=0x5e4e waiting for monitor entry [0x00007f486b7fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.Service.process(Example.java:42)
- waiting to lock <0x000000076e9a7d78> (a java.lang.Object)
- locked <0x000000076e9a7d88> (a java.lang.Object)注意两行关键信息:
-
- waiting to lock:当前线程想抢的锁地址 -
- locked:它自己已持有的锁(可能引发嵌套锁等待)
顺着waiting to lock的地址(比如0x000000076e9a7d78),全文搜索,找到哪个线程正在locked这个地址——那个线程就是“持锁者”。如果持锁者本身也处于BLOCKED或WAITING,就极可能是死锁或锁链阻塞。
Arthas里用thread -b快速定位死锁线程
如果怀疑是死锁,别手动翻dump,直接用Arthas:
启动arthas-boot.jar并 attach 到目标进程后,运行:thread -b
这个命令会自动扫描所有线程,输出明确的死锁报告,包括:
- 哪些线程互相等待
- 每个线程当前持有哪把锁、等待哪把锁
- 对应的Java堆栈位置(精确到行号)
比jstack末尾的Found one Java-level deadlock更直观,且支持实时刷新。注意:Arthas默认线程ID是十进制,而jstack里是十六进制,交叉验证时要转换(例如0x5e4e → 十进制24142)。
持锁线程长时间RUNNABLE但没释放锁?查safepoint或JNI阻塞
有时你会发现:A线程显示locked ,状态却是RUNNABLE,且堆栈停在某个方法里不动(比如FileInputStream.read或自定义JNI调用)。这时它很可能:
- 正执行一个不响应JVM safepoint的本地代码(如长耗时JNI、
System.arraycopy大数组拷贝) - 在做同步I/O(如阻塞式socket读写),而该操作不会触发safepoint检查
- 被操作系统调度器长时间剥夺CPU(低优先级+高负载)
这类情况jstack无法体现真实阻塞原因,需结合top -H -p <pid></pid>看对应线程的CPU/等待时间,并用perf top或async-profiler确认热点是否落在native层。
真正难排查的不是“谁拿了锁”,而是“拿锁的线程为什么迟迟不放”——它可能根本没在Java栈上执行,而是在OS或native世界里卡住了。


















