【线上问题】1分钟学会如何定位 Java 应用 CPU 飙升问题
在 Java 开发中,CPU 飙升是最棘手的线上问题之一 —— 日志可能无异常,监控仅显示 “CPU 100%”,但具体哪段代码导致问题却难以定位。阿里开源的 Arthas 工具能像 “手术刀” 般精准剖析问题,下面手把手教你用它排查。
一、准备工作:启动 Arthas 并关联目标进程
Arthas 是基于 JVM 的诊断工具,无需修改代码,可直接关联运行中的 Java 进程。
# 1. 下载 Arthas(阿里云 CDN,速度快)curl -O https://arthas.aliyun.com/arthas-boot.jar# 2. 启动并选择目标进程java -jar arthas-boot.jar
运行后会列出所有 Java 进程,输入目标进程的 PID(例如第 2 个进程输 2),按回车进入交互模式,此时 Arthas 已与目标 JVM 建立连接。
二、第一步:定位高 CPU 线程
CPU 飙升的本质是线程持续占用资源,用 thread
命令筛选高 CPU 线程:
# 查看 CPU 占用最高的前 3 个线程(按 CPU 使用率降序)thread -n 3
输出示例:
Threads Total: 45, NEW: 0, RUNNABLE: 10, BLOCKED: 0, WAITING: 20, TIMED\_WAITING: 15, TERMINATED: 0ID NAME STATE %CPU TIME15 pool-1-thread-3 RUNNABLE 98.5% 3m12s <-- 重点关注16 pool-1-thread-4 RUNNABLE 85.2% 2m45s8 GC task thread#0 RUNNABLE 5.3% 45s
关键解读:
-
优先关注
STATE
为RUNNABLE
的线程(正在运行,直接消耗 CPU)。 -
记录高 CPU 线程的 ID(如 15、16),用于下一步分析。
三、第二步:查看线程堆栈,定位具体代码
通过线程 ID 查看堆栈,定位消耗 CPU 的方法:
# 查看 ID=15 的线程堆栈thread 15
输出示例:
"pool-1-thread-3" Id=15 RUNNABLEat com.example.MyService.calculate(MyService.java:100) <-- 高 CPU 方法at com.example.MyController.process(MyController.java:50)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.lang.Thread.run(Thread.java:748)
关键解读:
-
堆栈顶部的方法(如
MyService.calculate
)是 CPU 消耗的源头。 -
注意代码行数(MyService.java:100),可直接定位到具体逻辑。
四、第三步:验证方法是否异常
用 monitor
命令统计方法调用频率和耗时,确认是否存在性能问题:
# 监控 MyService 类的 calculate 方法,每 5 秒输出一次统计monitor -c 5 com.example.MyService calculate
输出示例:
timestamp class method total success avg(ms) fail
2023-10-01 10:00 com.example.MyService calculate 1000 1000 500 0
关键解读:
-
若
total
(调用次数)极高(如每秒数百次),可能是调用过于频繁。 -
若
avg(ms)
(平均耗时)过长(如 500ms),可能是方法内部逻辑低效。
五、第四步:追踪方法内部调用链,找到瓶颈
若 monitor
显示方法耗时高,用 trace
命令追踪子调用,定位瓶颈子方法:
# 追踪 calculate 方法的调用链,显示每个子方法耗时trace com.example.MyService calculate
输出示例:
---ts=2023-10-01 10:01:00;thread_name=pool-1-thread-3;id=15---[500ms] com.example.MyService:calculate()+---[300ms] com.example.MyService:doHeavyCalculation() <-- 耗时最长---[200ms] com.example.Utils:processData()
关键解读:
-
子方法
doHeavyCalculation
耗时 300ms,是整个方法的瓶颈。 -
优先优化耗时最长的子方法。
六、第五步:反编译代码,查看具体实现
定位问题方法后,用 jad
反编译代码,直接分析逻辑(无需源码):
# 反编译 MyService 类的 doHeavyCalculation 方法jad com.example.MyService doHeavyCalculation
输出示例(简化):
public void doHeavyCalculation() {int result = 0;for (int i = 0; i < Integer.MAX_VALUE; i++) { <-- 死循环!result += i;}
}
关键解读:
-
直接查看代码实现,快速发现问题(如死循环、无限制递归、大计算量逻辑)。
七、第六步:生成火焰图,全局分析(可选)
若问题涉及多线程协同,用 profiler
生成 CPU 火焰图,直观展示全局热点:
# 开始 CPU 采样(默认持续 30 秒,按 Ctrl+C 可提前结束)profiler start# 停止采样并生成 HTML 火焰图profiler stop --format html
生成的 flamegraph.html
用浏览器打开后,横向越长的函数表示 CPU 占用越高,可快速定位全局热点。
八、常见 CPU 飙升问题及解决方案
问题现象 | 可能原因 | 解决方案 |
---|---|---|
单个线程 RUNNABLE,CPU 98% | 死循环、无限递归、大计算量逻辑 | 优化算法(如减少循环次数)、增加退出条件 |
多个线程 BLOCKED,CPU 飙升 | 锁竞争激烈(如全局锁) | 减小锁粒度(用 ReentrantLock 分段锁)、替换为无锁结构(如 ConcurrentHashMap) |
GC 线程 CPU 占比高(10%+) | 频繁 Full GC(内存泄漏、大对象频繁创建) | 用 |
总结:排查流程口诀
-
thread -n 3
→ 抓高 CPU 线程 -
thread [ID]
→ 看堆栈找方法 -
monitor
→ 统计调用频率和耗时 -
trace
→ 揪出耗时子方法 -
jad
→ 反编译看代码 -
profiler
→ 火焰图全局分析
按我给的这个流程,90% 的 CPU 问题可在 5 分钟内定位!以后遇到线上 CPU 飙升不用慌,Arthas 这套组合拳能快速解决我们的问题。
#Arthas #Java 诊断 #CPU 飙升 #线上问题排查