jstack是秒杀崩溃时首选工具,因其能在jvm未退出时快速抓取线程快照;通过jstack -l导出栈、grep筛选blocked线程锁地址、反查持有者三步精准定位锁竞争根因。
一、为什么秒杀崩溃时 jstack 是第一响应工具
电商秒杀场景下,线程大量 blocked 的本质是锁竞争激增,而非 cpu 或内存耗尽。jstack 不依赖 jvm 启动参数(如 -xx:+printgcdetails),只要进程还在运行(哪怕已无响应但未 oom 退出),就能瞬间抓取线程快照——这是 jstack 不可替代的“现场保全”能力。
二、三步精准定位 BLOCKED 线程持有的锁地址
第一步:快速导出全量线程栈
在应用服务器上执行:
jstack -l
注意加 -l 参数,它会输出显式锁(如 ReentrantLock)的 AQS 队列信息和 synchronized 锁的 owner 地址,缺它就无法追踪锁归属。
第二步:筛出 BLOCKED 线程并提取锁地址
用 grep 快速聚焦:
grep -A 5 "java.lang.Thread.State: BLOCKED" thread_dump.log
重点关注每段 BLOCKED 线程日志末尾的:
- waiting to lock (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
或
- waiting to lock (a com.example.order.service.OrderService)
这个 0x000000071a2b3c40 就是锁对象的内存地址,记下来。
第三步:反查谁持有该锁
全文搜索该地址:
grep -B 10 "0x000000071a2b3c40" thread_dump.log | grep "locked"
你会看到类似:
- locked (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
往上翻几行,找到对应线程名(如 “http-nio-8080-exec-12”)及其堆栈——它就是罪魁祸首,正在执行 lock() 后未释放,或卡在临界区某行代码(比如数据库慢查询、远程调用超时)。
三、实战中必须避开的 3 个坑
- 别用 jstack 不带 -l:默认不显示锁地址,BLOCKED 线程只显示 “waiting for monitor entry”,无法关联到具体锁实例
- 别在高峰期反复执行 jstack:单次执行虽快,但高频 dump 会短暂挂起所有线程(Stop-The-World 轻量级),加剧雪崩;建议先用 ps -mp
-o THREAD,tid,time 查高 CPU 线程,再针对性 jstack - 别把锁地址当绝对值:JVM 每次重启地址重置,跨 dump 文件不可比对;同一份 dump 内才具意义
四、锁定后下一步:快速验证与临时止损
确认持锁线程后,立刻检查其堆栈最后一行:
— 如果是 JDBC PreparedStatement.execute 卡住 → 查数据库连接池是否耗尽、SQL 是否缺失索引
— 如果是 FeignClient 调用 卡住 → 检查下游服务状态、Feign 超时配置是否过长(如 readTimeout=60000)
— 如果是 ReentrantLock.lock() 后无 unlock() → 看是否 try-finally 缺失,或 lock() 后抛异常跳过 unlock()
临时缓解(仅限应急):
• 若持锁线程明显僵死(如 blocked > 30s,且堆栈停在 IO),可用 kill -3
• 切忌直接 kill 线程(Java 不支持),应通过降级开关关闭秒杀入口,或调整限流阈值,让锁自然释放。











