用jstack抓快照配合top定位高cpu线程,重点分析runnable态线程的栈顶调用,识别死循环、无节制计算或流式处理等根源,注意排除gc线程与阻塞态伪runnable干扰。

直接用 jstack 抓线程快照,配合 top 定位高 CPU 线程,再聚焦其调用栈,就能快速揪出死循环和 CPU 飙升的根源。关键不是堆栈有多长,而是看“谁在跑、跑什么、停不停”。
确认目标进程 PID 并抓取带锁信息的快照
先用 jps -l 或 ps -ef | grep java 找到 Java 进程 ID,注意区分多个实例;启动用户不同会导致权限失败,可用 sudo -u [user] jstack -l [pid] 切换身份执行。JDK 8u261+ 默认禁用 attach,若报 Unable to deduce java executable,需在 JVM 启动参数中加 -XX:+StartAttachListener。
抓快照务必加 -l 参数:它能显示锁持有与等待关系,对后续判断是否真在自旋或卡在同步块里至关重要。命令示例:
- jstack -l 12345 > threaddump.log
- 如进程响应迟缓,可加 -F 强制获取(但会触发全局 safepoint,慎用)
筛选 RUNNABLE 线程并定位高耗 CPU 的 nid
CPU 飙升几乎总由大量或单个 RUNNABLE 状态线程持续执行引起。注意:RUNNABLE 不等于“正在计算”,要看栈顶最后一行——如果写着 at java.lang.Object.wait(Native Method) 或 parking to wait for,实际是阻塞态,可排除。
实战步骤:
- 用 top -Hp [pid] 查看进程中各线程 CPU 占用,按 P 排序,记下最高占用的十进制 tid(比如 28947)
- 转为十六进制:printf "%x\n" 28947 → 得到 0x7113
- 在快照中搜索:grep -A 10 "nid=0x7113" threaddump.log,重点关注其 stack trace 最顶端的 at 行
识别典型死循环与高负载代码特征
找到高 CPU 线程后,看它的调用栈末尾是否落在以下位置:
- 无限循环体内部:如 at com.example.LoopController.loop(LoopController.java:15),且方法内含 while(true) 或条件恒为真(如
size == 0未更新) - CPU 密集型操作无节制:正则匹配超长文本、
String.replaceAll回溯爆炸、JSON.parseObject解析巨量数据、HashMap.get在极端 hash 冲突下退化为链表遍历 - 未设限的流式处理:如
Stream.iterate(...).limit(...)缺少 limit,或collect(Collectors.toList())加载超大数据集到内存
特别注意行号(如 LoopController.java:15)真实有效——前提是编译时保留了调试信息(Maven 默认开启)。
排除干扰:别把 GC 或系统线程当业务问题
有些 RUNNABLE 线程并非业务逻辑导致 CPU 高,而是 JVM 自身行为:
- 看到大量 GCTaskThread、ConcurrentMarkThread 或 VM Thread 处于 RUNNABLE,大概率是 GC 压力大(如频繁 Young GC 或 CMS/G1 并发标记阶段),应查 GC 日志而非业务代码
- JNI 调用或 native IO 阻塞也可能表现为 RUNNABLE,此时可加 -e 参数(JDK 8u60+)输出 native 堆栈辅助判断
- 若线程栈顶是 Unsafe.park、Object.wait 或 SocketInputStream.read,说明它实际在等资源,不属于 CPU 消耗源











