导读:本期聚焦于Amelis创作的《线程Dump分析实战:如何快速定位CPU飙升与死锁问题》,敬请观看详情。当Java应用突然响应变慢、CPU占用飙升甚至完全假死时,线程Dump往往是最直接有效的排查证据。本文以实战为核心,先讲清楚如何用jstack和jcmd在不同环境下抓取线程快照,再逐行解读线程状态、调用栈与锁信息的含义,重点剖析RUNNABLE、BLOCKED、WAITING几种典型状态的判断方法。文中通过死锁、连接池耗尽、死循环三个真实场景的Dump片段,演示从线程名到栈顶代码的完整定位路径,最后总结一套可复用的分析步骤,帮助你拿到Dump文件后几分钟内锁定问题根源。

线上服务突然卡住不动,日志不刷了,请求全部超时,这时候你手头最趁手的武器是什么?答案多半是线程Dump。它相当于给JVM进程拍了一张快照,把所有线程在那一瞬间的状态和执行位置全部记录下来。但快照拍出来只是第一步,成百上千个线程堆栈摆在眼前,怎么看出门道才是关键。这篇文章就围绕一次完整的线程Dump分析流程展开,从抓取、解读到定位,把每一步都讲透。

线程Dump分析实战:如何快速定位CPU飙升与死锁问题

一、如何正确抓取线程Dump

抓取Dump最常用的工具是JDK自带的jstack。先通过jps或者ps -ef | grep java拿到进程号,然后执行jstack命令即可把所有线程的堆栈输出到文件。要注意的是,如果目标是运行在Docker容器里的进程,需要在容器内部执行,或者借助宿主机的nsenter工具进入进程命名空间,否则会报找不到进程的错误。

# 找到Java进程
jps -l

# 抓取线程Dump并保存
jstack -l 12345 > /tmp/thread_dump_1.txt

# 等待5秒后再抓一份,用于对比
sleep 5
jstack -l 12345 > /tmp/thread_dump_2.txt

连续抓两到三份Dump是一个非常重要的习惯。因为快照只能代表某个瞬间,某个线程恰好在你抓取的那一刻在等锁,不代表它真的卡死了。多份Dump对比,如果同一个线程的栈顶位置完全没变,才能确认它真的堵在那里。另外如果jstack命令本身长时间没反应,说明JVM进程可能已经假死,可以尝试加-F参数强制抓取,虽然强制模式下部分锁信息可能丢失,但至少能拿到线程栈。

除了jstack,还可以用jcmd来抓取,命令是jcmd 12345 Thread.print -l,效果基本一致。在容器环境或者只能访问标准输出的场景下,向进程发送SIGQUIT信号也能触发Dump输出,即执行kill -3 12345,JVM会把线程信息打印到stdout日志中,进程本身不会退出。这招在Kubernetes环境里配合kubectl logs查看非常方便。

二、读懂线程Dump中的关键信息

拿到Dump文件后,每一段线程信息的结构大致是:线程名、是否守护线程、优先级、线程ID、原生线程ID(nid)、线程状态,以及完整的调用栈。其中线程名和nid是最先要看的两个信息。规范的框架会给线程起有意义的名字,比如Tomcat的工作线程叫http-nio-8080-exec-1,Dubbo的叫DubboServerHandler-xxx,看到大量同类名字的线程都卡在同一个位置,问题范围基本就锁定了。

"http-nio-8080-exec-3" #47 daemon prio=5 os_prio=0 tid=0x00007f8b0c012800 nid=0x3d2f waiting on condition [0x00007f8ae23bd000]
   java.lang.Thread.State: TIMED_WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x000000078c5a2b08> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
	...

线程状态是解读的核心,常见的有几种。RUNNABLE并不一定表示在消耗CPU,线程在做网络IO时状态也是RUNNABLE,但实际是在等数据返回;BLOCKED表示线程在等待进入一个synchronized块,即抢锁失败,Dump里会明确写出waiting to lock <0x...>以及哪个线程持有这把锁;WAITINGTIMED_WAITING通常是线程在Condition上等待资源,比如从连接池借连接、从队列取任务。WAITING状态的线程本身不一定是异常,空闲线程池里的线程大部分都在WAITING等任务,关键看它等的对象是什么。

三、通过Dump配合top定位CPU飙升

CPU飙升场景需要把操作系统层面的线程和Dump里的线程对应起来。先用top -Hp 12345查看进程内哪个线程占用CPU最高,把线程号换算成十六进制,然后到Dump文件里搜索对应的nid,就能直接找到那个吃CPU的线程在执行什么代码。假设top里看到线程号15662占用98%的CPU,换算成十六进制是3d2e,在Dump里搜索nid=0x3d2e即可。

# 查看进程内各线程CPU占用
top -Hp 12345

# 假设最耗CPU的线程号是15662,转十六进制
printf "%x\n" 15662
# 输出 3d2e

# 在Dump中定位该线程
grep -A 30 "nid=0x3d2e" /tmp/thread_dump_1.txt

定位到线程后看栈顶。如果栈顶是自己的业务代码,比如某个循环计算逻辑,多半是死循环或者算法复杂度出了问题;如果栈顶是正则相关的类如java.util.regex.Pattern$Curly.match,可能是正则表达式发生了灾难性回溯;如果栈顶是GC线程或者频繁出现Unsafe.park这类调用,则要考虑是不是JVM层面的内存问题导致的CPU升高。同样建议连续抓多份,栈顶代码段一直不变,才是真凶。

四、死锁的识别与分析

死锁是Dump分析里最令人愉快的一种情况,因为JVM自带死锁检测,如果存在死锁,Dump文件末尾会直接打印出一段诊断信息,明确告诉你线程1持有锁A等待锁B,线程2持有锁B等待锁A,形成环路。同时两个涉事线程的状态都是BLOCKED,各自标注了持有和等待的锁地址。这时候顺着锁地址互相搜索,把两个线程的完整调用栈拉出来,加锁的代码位置就一目了然了。

解决死锁的常规思路有三个:一是统一加锁顺序,保证所有线程按相同顺序获取锁,环路自然打破;二是改用tryLock带超时的获取方式,拿不到锁就释放自己已持有的锁重试;三是评估业务上是否真的需要两把锁,很多时候嵌套加锁是设计不合理造成的。修复后可以借助压测复现场景,再次抓Dump确认死锁信息消失。

还有一种隐性的类初始化死锁也值得警惕:两个线程分别在初始化两个不同的类,而各自的静态初始化块里又互相引用了对方类,JVM的类初始化锁同样会形成环路。这种场景在Dump里表现为线程栈顶停留在<clinit>方法,末尾同样会有死锁报告,排查时注意区分它与普通对象锁死锁的区别。

五、总结一套可复用的分析流程

把前面的内容串起来,可以固化成五步。第一步看Dump末尾有没有死锁诊断,有就直接处理;第二步搜索最常见的可疑状态,比如大量BLOCKED在同一个锁地址上,或者业务线程全部WAITING在数据库连接池;第三步用top加nid的方式对号入座,解决CPU类问题;第四步多份Dump对比,确认线程卡点是否持续存在,排除瞬间抖动的干扰;第五步结合业务线程名分组统计,比如统计http-nio开头的线程状态分布,快速判断Web层是否整体阻塞。

实际排查中还可以配合一些辅助手段提升效率,比如用fastThread、jca这类工具做可视化分析,自动统计线程状态分布和重复栈。但工具只是加速器,底层逻辑还是要靠自己理解线程状态与调用栈的含义。把这套流程练熟,下次线上服务卡顿,从抓取Dump到给出结论,十几分钟足够了。

线程Dump分析jstack使用死锁排查修改时间:2026-09-15 01:34:58

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260915/56959.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。