查线程状态必须用jcmd thread.print,它输出线程名、状态、锁信息等,比jstack轻量且无需attach权限;虚拟线程需加-all参数。

查线程状态:用 jcmd <pid> VM.native_memory</pid> 不行,得用 jcmd <pid> Thread.print</pid>
很多人一上来就试 jcmd <pid> VM.native_memory</pid>,以为能看线程,结果只看到堆外内存——这根本不是线程状态。真正要看 JVM 里每个线程在干啥、卡在哪、是不是 WAITING 或 BLOCKED,必须用 Thread.print。
它输出和 jstack <pid></pid> 类似,但更轻量,不依赖外部工具,且是 JDK 7u40+ 原生支持的命令。
-
jcmd不需要额外 attach 权限(不像jstack在某些容器里常被拒) - 输出里会标出线程名、
java.lang.Thread.State、锁持有/等待对象、是否为 daemon 线程 - 注意:如果应用用了虚拟线程(JDK 21+),
Thread.print默认不显示它们;要加-all参数才显示全部 Loom 线程
定位高 CPU 线程:先 jcmd <pid> VM.native_memory summary</pid> 再结合 top -H -p <pid></pid>
jcmd <pid> VM.native_memory</pid> 本身不提供线程级 CPU 消耗,它只告诉你 native 内存分布。真要揪出哪个 Java 线程吃 CPU,得靠 OS 层配合。
标准做法是两步走:
- 用
top -H -p <pid></pid>找出占用 CPU 最高的线程 ID(LWP),记下十进制数 - 把那个数转成十六进制(比如 12345 → 0x3039),再在
jcmd <pid> Thread.print</pid>输出里搜0x3039,就能定位到具体线程栈 - 别直接用
jstack替代——有些生产环境禁用jstack,但jcmd往往开着
导出实时运行数据:jcmd <pid> VM.info</pid> 和 VM.system_properties 要搭配着看
单看 VM.info 只给 JVM 启动参数和基本运行时信息(比如 GC 类型、JIT 编译器),看不出当前负载;而 VM.system_properties 里藏着关键线索,比如 java.version、sun.cpu.isalist(影响 JIT 行为)、甚至自定义的 app.env 配置项。
-
VM.info输出里的CompilerOracle行,说明有没有手动排除某些方法 JIT,会影响性能热点判断 - 如果看到
UseContainerSupport: true,就得小心MaxRAMPercentage是否设得太低,导致频繁 GC——这时要同步查jcmd <pid> VM.flags</pid> - 注意:
VM.system_properties不包含环境变量,PATH或LD_LIBRARY_PATH还得去容器或启动脚本里翻
常见失败场景:权限、JDK 版本、容器隔离三重坑
jcmd 看似简单,但在实际排查中经常“没反应”或“拒绝访问”,核心就三个原因。
- 非 root 用户执行时,目标 JVM 是另一个用户启的(Linux 下默认禁止跨用户 jcmd);解决办法:要么切到同用户,要么用
sudo -u <user> jcmd ...</user> - JDK 8u60 之前版本不支持
Thread.print,只认VM.native_memory和VM.info;确认方式:jcmd <pid> help</pid>看输出列表有没有Thread.print - Kubernetes Pod 里用 distroless 镜像时,
jcmd命令可能根本不存在;得提前在基础镜像里装好openjdk-jdk(不只是 JRE),或者改用jdk-toolingsidecar
线程状态和 CPU 关联性容易被日志掩盖——比如一个线程明明 BLOCKED 在锁上,但日志还在刷 INFO,这时候光看 log 会误判为“正常运行”。必须以 jcmd 输出为准,尤其关注 parking to wait for 和 waiting on condition 这类提示。








