top -h 不能直接定位死循环,仅能筛选高cpu线程;需结合jstack(tid转十六进制匹配nid)、perf或async-profiler分析栈帧与热点函数才能准确定位。

top -Hp 找不到高 CPU 线程?检查是否启用了线程模式
Linux 的 top 默认显示进程视图,不直接暴露线程。必须加 -H 参数才能切到线程级视图——很多人漏掉这步,还在进程列表里反复翻找,浪费时间。
执行前先确认目标 Java 进程 PID(比如用 ps -ef | grep java 或 top -c | grep java),再运行:
jstack 12345 | grep -A 20 'nid=0x'
常见误区:
- 误用
top -p 12345:这只是聚焦进程,没开线程视图 - 按了
H键但没生效:交互式 top 中需先按H,再按P(大写)按 %CPU 排序;但脚本化排查更稳,直接用top -Hp 12345 - 看到的 %CPU 加起来超 100%:正常,多核下每个线程的 CPU 占用是相对于单核的,300% 表示占满 3 个核
printf "%x" 转换后搜不到 nid?注意大小写和前缀
jstack 输出里的线程 ID 字段叫 nid,格式固定为 0x 开头、小写字母的十六进制(如 0x3a7f)。而 printf "%x" 默认输出小写无前缀,所以不能直接搜 3a7f,得搜 0x3a7f 或 3a7f(取决于你用的 grep 模式)。
实操建议:
- 转换命令统一用:
printf "0x%x\n" 14463→ 直接输出带0x前缀的小写结果 - 搜索时用:
jstack 12345 | grep -A 30 '0x3a7f',避免匹配到其他十六进制数字(比如内存地址) - OpenJDK 8/11/17 行为一致,但某些定制版 JDK 可能省略
0x,此时只搜3a7f更保险
jstack 报 “Unable to open socket file”?权限或 JDK 冲突
这个错误不是堆栈本身的问题,而是 jstack 连不上目标 JVM。最常见两个原因:
- 当前用户和 Java 进程用户不一致(比如 Java 是
www用户启的,你用root执行jstack)→ 改用sudo -u www jstack 12345 - 系统装了多个 JDK(比如同时有 OpenJDK 和 Oracle JDK),
jstack版本和目标 JVM 不匹配 → 先查进程用的 JDK:readlink -f /proc/12345/exe,再用对应 JDK 下的jstack(如/usr/lib/jvm/java-11-openjdk-amd64/bin/jstack 12345) - 极少数情况是目标 JVM 启动时加了
-XX:+DisableAttachMechanism,这时只能用jstack -F强制抓取(会短暂暂停 JVM)
找到 nid 对应的栈,但全是 native 方法?重点看 RUNNABLE + socketRead0 / poll / epollWait
如果堆栈顶是 socketRead0、epollWait、poll 这类 native 调用,且状态是 RUNNABLE,别急着跳过——这往往说明线程卡在 I/O 等待,但背后可能是业务层没设超时、连接池耗尽、或下游服务假死。
关键线索:
- 看线程名:
http-nio-8080-exec-23或ForkJoinPool.commonPool-worker-5能快速定位归属模块 - 往下翻几行,找最近的业务代码行,比如
com.example.api.OrderService.process(OrderService.java:88) - 若全是
Unsafe.park或LockSupport.park,状态却是RUNNABLE,大概率是 JDK 并发 bug(如ThreadPoolExecutor.getTask()忙等),需结合 JDK 版本查已知 issue
真正难的是那些“看起来正常”的栈:RUNNABLE 状态、没明显阻塞点、但 CPU 就是打满。这时候得怀疑是不是死循环里调了轻量级操作(比如空 while 循环 + volatile 读),jstack 抓不到现场,得配合 async-profiler 或 perf 火焰图进一步下钻。











