一、如何定位线上服务 CPU 飙升问题?——从进程到代码行的完整排查流程
最近天天邮件90%,真烦,决定撤底解决一下。本文记录排查流程,从进程 → 线程 → 代码堆栈,层层深入,最终锁定“元凶”。
本文适合有一定 Linux 运维基础、熟悉 Java 或其他 JVM/多线程语言的开发者阅读。
二、第一步:登录服务器,查看高 CPU 进程
使用 top 命令查看系统整体 CPU 使用情况:
top
在输出中,按 P(大写) 可按 CPU 使用率降序排列。你会看到类似:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12345 deploy 20 0 8542340 2.1g 18744 S 108.3 13.2 123:45 java
这里的关键信息:
- PID = 12345:高 CPU 占用的进程 ID。
- %CPU = 108.3:说明该进程使用了超过 1 个 CPU 核心(多线程并发)。
✅ 记下这个 PID,它是后续排查的起点。
三、第二步:查看该进程中高 CPU 的线程
一个进程包含多个线程。我们需要找出是哪个线程在“作妖”。
使用 ps 命令查看该进程下的所有线程及其 CPU 占用:
ps -mp 12345 -o THREAD,tid,time,%cpu
输出示例:
USER TID %CPU TIME
deploy 28512 15.2 12:34
deploy 28513 2.1 3:21
deploy 28514 0.5 1:02
...
- TID = 28512:线程 ID(十进制)。
- %CPU = 15.2%:该线程占用了大量 CPU。
✅ 找到 CPU 占用最高的 TID(如 28512)。
四、第三步:将 TID 转换为十六进制
JVM 的线程 dump(jstack)中,线程 ID 是以 十六进制 形式显示的。因此需要转换:
printf "%x\n" 28512
输出:
6f60
✅ 记下十六进制 TID:
0x6f60(实际使用时去掉0x,直接用6f60)。
五、第四步:抓取线程堆栈,定位代码
使用 jstack(适用于 Java 应用)导出当前进程的线程快照:
jstack 12345 > jstack.log
然后在 jstack.log 中搜索十六进制 TID:
grep -A 30 "6f60" jstack.log
你会看到类似内容:
"Thread-123" #68 prio=5 os_prio=0 tid=0x00007f8a6f60 nid=0x6f60 runnable [0x00007f8a12345000]
java.lang.Thread.State: RUNNABLE
at com.example.service.DataProcessor.process(DataProcessor.java:45)
at com.example.service.DataProcessor.lambda$handle$0(DataProcessor.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
🎯 关键信息:
- 线程名:
Thread-123- 代码位置:
DataProcessor.java第 45 行- 状态:
RUNNABLE(正在运行,可能死循环或高频计算)
六、分析可能原因
常见导致 CPU 飙升的原因包括:
- 死循环(如 while(true) 忘记 break)
- 高频 I/O 或反射操作(如频繁 JSON 序列化)
- 正则表达式回溯爆炸
- HashMap 在高并发下形成死链(旧版 JDK)
- 自旋锁或 busy-wait 逻辑
在你的代码中,重点检查堆栈顶部的方法逻辑。
七、补充:非 Java 应用怎么办?
- Go/C++/Python:可用
perf工具采样:
或生成火焰图(Flame Graph)分析热点函数。perf top -p 12345 - 通用方法:
strace -p <PID>查看系统调用,但可能影响性能,慎用。
总结:排查流程图
CPU 飙升报警
↓
top → 找到高 CPU PID
↓
ps -mp <PID> → 找到高 CPU TID(十进制)
↓
printf "%x" <TID> → 转十六进制
↓
jstack <PID> | grep <hex TID> → 定位代码行
↓
修复代码 + 压测验证
示例场景回顾
假设你发现:
- PID = 285
- TID = 28512(十进制)→ 十六进制 =
6f60 - jstack 显示线程卡在
FileReader.read()死循环
那你就可以立即检查该读取逻辑,很可能是未正确处理 EOF 或缓冲区。
希望这篇整理能帮助大家在面对 CPU 飙升问题时,不再手忙脚乱。性能问题不可怕,可怕的是没有排查路径。掌握这套方法,你就能在“甜蜜约会”之余,依然做团队中最稳的救火队员 💪!
欢迎在评论区分享你的排查经历,或者提出更复杂的场景,我们一起攻克!
881

被折叠的 条评论
为什么被折叠?



