jstack是JDK自带的轻量级线程快照工具,适用于生产环境快速定位假死线程;需先确认假死特征(如CPU低、日志停更),再用jstack -l -e精准抓取,聚焦WAITING/BLOCKED/RUNNABLE等四类状态,并通过差分法识别动态变化。

jstack 是 JDK 自带的轻量级线程快照工具,无需额外依赖、不侵入应用、不重启服务,特别适合在生产环境快速定位“假死”(如长时间阻塞、无限等待、CPU空转但无进展)的线程。关键不是等它“出问题”,而是用对时机和方式——秒级捕捉的核心在于:精准触发 + 快速过滤 + 状态聚焦。
一、先确认是不是真“假死”,再决定要不要 jstack
假死 ≠ CPU 高或响应慢,典型表现是:接口超时但 CPU 使用率低、日志停更、部分请求卡住不动、线程数稳定但业务无进展。此时别急着 dump,先做两件事:
- 用 top -H -p <pid> 查看各线程的 CPU/运行时间,找长期处于
R(运行中)但实际没推进,或长期S(睡眠)却未唤醒的可疑线程 ID(LWP) - 用 cat /proc/<pid>/status | grep Threads 看线程总数是否异常增长(可能泄漏),或稳定在高位但业务无流量(可能集体阻塞)
二、jstack 命令要加参数,否则容易抓偏
默认 jstack <pid> 只输出 Java 线程栈,但假死常发生在本地方法(JNI)、锁竞争、IO 等系统层。必须加上:
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
-
-l:显示详细锁信息(包括
java.util.concurrent中的 AQS 队列状态、ReentrantLock 的持有者与等待者) - -e(JDK 8u60+):导出额外的本地线程信息(如 JVM 内部线程、GC 线程状态),有助于识别 safepoint 卡顿或 JNI 阻塞
- 组合命令:jstack -l -e <pid> > thread-dump-$(date +%s).log(加时间戳防覆盖)
三、三秒内锁定“假死线程”的关键状态特征
拿到 dump 后别通读,直奔以下四类高危模式(每类 2–3 秒可扫完):
-
WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject:说明线程在
await()上挂起,需检查是否 signal 被漏发、条件永远不满足、或 await 在错误锁上 - BLOCKED on lock <0x...> + 下方有线程 Locked ownable synchronizers: [ReentrantLock]:典型锁竞争,看谁持锁不放、持锁后做了什么(比如调了远程接口又没超时)
- RUNNABLE with native method (e.g., socketRead0, epollWait):看似运行中,实则卡在系统调用;结合 strace 可验证,常见于无超时的 Socket 连接或 NIO Selector 长期无事件
- in Object.wait() with no notify/notifyAll in sight:老式 wait/notify 模式下,可能 notify 被跳过、条件判断错位,或 notify 发给了错误对象
四、一次 dump 不够?用“差分法”抓动态变化
单次 dump 只是快照,假死若偶发或短暂,建议 3–5 秒连续采样两次:
- 执行:jstack -l <pid> > a.log && sleep 3 && jstack -l <pid> > b.log
- 对比差异:diff <(grep 'java.lang.Thread.State' a.log | sort) <(grep 'java.lang.Thread.State' b.log | sort)
- 重点关注:从
RUNNABLE→WAITING却没再变回来的线程,或反复在BLOCKED和RUNNABLE间切换但始终无法获取锁的线程

















