jcmd Thread.print -l 输出线程堆栈及锁状态快照,-l 选项明确显示 java.util.concurrent 锁和 synchronized 的持有者与等待者,格式与 jstack -l 一致但地址更规范、兼容性更好。

什么是 jcmd Thread.print -l 的真实输出内容
它不是“导出报告”,而是向标准输出打印当前 JVM 中所有线程的堆栈 + 锁状态快照,-l 选项的关键作用是补充显示:哪些线程持有 java.util.concurrent.locks.AbstractOwnableSynchronizer(如 ReentrantLock)或 synchronized 块的锁,以及哪些线程正在等待这些锁。
注意:它不会显示 java.util.concurrent 内部队列节点(如 AbstractQueuedSynchronizer$Node)的详细链表结构,也不会解析锁的公平性、条件队列等元信息——只是“谁持有了什么锁”和“谁在等这个锁”的直接映射。
如何正确捕获完整输出并避免截断或乱码
直接运行 jcmd <pid> Thread.print -l 可能因终端宽度/缓冲区限制导致长堆栈被折行、锁地址被截断,甚至中文路径或日志出现乱码(尤其 Windows 上 cmd 默认编码非 UTF-8)。
- 用重定向强制保存原始字节:
jcmd <pid> Thread.print -l > thread-l.locks.txt 2>&1 - Linux/macOS 下建议加
stdbuf -oL -eL避免行缓冲丢失:stdbuf -oL -eL jcmd <pid> Thread.print -l > thread-l.locks.txt - Windows PowerShell 中改用:
jcmd <pid> Thread.print -l | Out-File -Encoding UTF8 thread-l.locks.txt(避免 cmd 的 GBK 编码污染) - 不要依赖
jconsole或jvisualvm的“线程 dump”按钮——它们默认不带-l,且 GUI 截取可能丢掉锁字段
怎么从输出里快速定位死锁或锁竞争热点
jcmd 的 -l 输出中,每个线程块末尾会追加类似这样的锁段:
Locked ownable synchronizers:
- <code>java.util.concurrent.locks.ReentrantLock$NonfairSync</code>@7f8b1a2c (a java.util.concurrent.locks.ReentrantLock)
- <code>java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock</code>@7f8b1b4d (a java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock)而等待线程则会在堆栈帧中标明:java.lang.Thread.State: BLOCKED (on object monitor) 或 java.lang.Thread.State: WAITING (parking),并紧跟一行 - waiting to lock <0x00000007f8b1a2c>。
- 搜索
waiting to lock定位阻塞点,再反查哪个线程持有对应地址的锁(地址需去掉<>和前缀,如0x00000007f8b1a2c→7f8b1a2c) - 用
grep -A 20 "BLOCKED\|WAITING.*parking" thread-l.locks.txt快速提取可疑线程上下文 - 注意区分
synchronized锁(object monitor 地址)和ReentrantLock(AQS sync 对象地址)——两者地址空间不同,不能跨类型匹配 - 若发现多个线程循环等待(A 等 B 持有的锁,B 等 C,C 又等 A),这就是典型死锁;
jcmd不会自动标出,得人工串地址
为什么不用 jstack -l?两者的实际差异在哪
jstack -l <pid> 功能几乎等价,但存在几个关键区别:
-
jcmd是 JDK 7u4+ 引入的统一诊断命令,无需额外 JVM 参数(如-XX:+UsePerfData)即可工作,而老版本jstack在某些容器或受限环境可能失败 -
jcmd输出中锁地址格式更稳定(始终为十六进制无前缀),jstack在某些 JDK 版本中会输出0x...或省略前导零,给正则匹配带来麻烦 -
jcmd支持非本地进程(通过 JMX 远程连接),jstack默认只支持同用户、同主机的 attach - 但
jstack -l在 JDK 8u60 之前对StampedLock持有状态识别有缺陷,jcmd Thread.print -l从 JDK 9 起才完整支持——如果分析的是较新并发原语,务必确认 JDK 版本
锁资源持有关系本身是瞬时快照,没有“深度”可言;所谓“深度堆栈”只是指它展示了每一层调用直到最顶层的 run() 或 get(),真正容易被忽略的是:它不记录锁获取时间、不标记是否可重入、也不体现锁粒度设计问题——这些得结合代码逻辑和监控指标交叉判断。

















