Java 服务 CPU 打满排查笔记
记录时间:2026-09-19
环境:Linux 服务器 / Java 服务
一、问题现象
线上 Java 服务 CPU 飙到 400%,接口卡死。我没急着重启,先保住现场,再顺着”进程 → 线程 → 堆栈 → 代码行”往下查。
二、排查过程
2.1 找进程 PID
top
top 里按 CPU 从高到低排,最顶上那个 Java 进程就是目标,这次拿到的是 11119。
2.2 找高 CPU 线程
top -Hp 11119
找到 CPU 最高的线程,记下它的 ID,这次是 11146。
2.3 线程 ID 转十六进制
printf "%x\n" 11146
输出 2b8a,就是 11146 转成十六进制的值。
2.4 jstack 抓堆栈定位代码行
jstack 11119 | grep -A 5 -i "0x2b8a"
输出示例:
"pool-1-thread-1" #16 prio=5 os_prio=0 cpu=9945233.19ms elapsed=11592.52s tid=0x00007f4128005ba0 nid=0x2b8a runnable [0x00007f41d0942000]
java.lang.Thread.State: RUNNABLE
at OrderService.lambda$main$0(OrderService.java:11)
堆栈直接把 OrderService.java:11 指出来了,卡死 CPU 的就是这一行。
2.5 进阶用法
单次 grep 能定位,但这几种场景我会换命令:
| 场景 | 命令 | 作用 |
|---|---|---|
| 保留完整现场 | jstack -l 11119 > jstack_dump.txt |
全部线程存文件慢慢看,-l 多打锁信息,查死锁要用 |
| 判断持续还是偶发 | 隔 3 秒连抓 3 次 jstack | 对比三次栈顶,区分持续死循环和偶发尖刺 |
| 查死锁无响应 | jstack -l 11119 \| grep BLOCKED |
直接过滤阻塞线程,找互相等待的链条 |
三、根因分析
我看 jstack 输出,心里先有数了:线程状态是 RUNNABLE,CPU 时间片累计 995 秒,说明它一直在跑、没阻塞过。问题落在 OrderService.java:11,是个 Lambda 里的逻辑。持续吃 CPU 又不阻塞,基本就两种:死循环,或者单次计算量太大。翻一眼第 11 行,是哪种一看就明白。
四、验证
我核对了一遍:堆栈里的 nid=0x2b8a 跟 printf 转出来的十六进制对得上,OrderService.java:11 也在最近改动的文件里,到这里基本能确定就是它了。
五、注意事项
- 先保现场,别急着重启。一重启线程堆栈就没了,再想定位只能等下一次复现。
top -Hp里的线程 ID 是十进制,jstack里的nid是十六进制,两边对不上号,必须先转成十六进制才能 grep。jstack得用启动服务那个用户跑,不然报 attach 失败,权限对不上。- 一次 grep 没匹配到别慌,热点线程可能会换,多抓几次对比栈顶就行。
- JDK 版本要跟起服务的一致,拿 A 版的
jstack抓 B 版起的进程,直接报错拿不到堆栈。 - CPU 已经打满的时候别反复抓
jstack,它本身也占 CPU。抓一两次把 dump 落盘就收手。 - 栈里状态是 RUNNABLE,就是在占着 CPU 空跑。九成 CPU 飙高都栽在这个状态下的死循环上,优先盯它。