场景分析题-有一台机器,部署了 Java 应用以后,发现它这个 CPU 持续被打满,现在需要排查定位,应该怎么做?
·
第一步:宏观定位 - 找到问题进程和线程
1. 找到最耗 CPU 的 Java 进程
# 按 CPU 使用率排序,显示进程 ID 和命令
top -c
# 或者使用更直观的 htop(如果已安装)
htop
观察要点:
-
找到 CPU 使用率最高的 Java 进程,记录其 PID
2. 找到该进程中最耗 CPU 的线程
# 查看指定进程中各个线程的 CPU 使用情况
top -H -p <java_pid>
# 或者
ps -eL -o pid,tid,pcpu,pmem,args | grep <java_pid> | sort -nr -k3 | head -10
观察要点:
-
记录消耗 CPU 最高的几个线程的 TID(十进制)
-
将这些 TID 转换为十六进制(为后续 jstack 分析做准备)
第二步:深入分析 - 查看线程在做什么
3. 使用 jstack 分析线程堆栈
# 获取 Java 进程的线程堆栈
jstack <java_pid> > jstack.log
# 或者在容器环境中(如果 jstack 不可用)
kubectl exec -it <pod_name> -- jstack <java_pid> > jstack.log
4. 关联高 CPU 线程与代码
在 jstack.log 中搜索之前转换的十六进制 TID:
# 在堆栈文件中搜索特定线程
cat jstack.log | grep -A 20 <hex_tid>
常见的高 CPU 线程模式:
-
死循环:线程一直处于
RUNNABLE状态,重复执行相同方法 -
密集计算:数学运算、加密解密、序列化等
-
GC 线程繁忙:频繁的垃圾回收
-
锁竞争:线程在
BLOCKED状态等待锁
第三步:专业工具 - 使用性能分析工具
5. 使用 jstat 检查 GC 情况
# 每 1 秒采样一次 GC 情况,共 10 次
jstat -gcutil <java_pid> 1s 10
观察要点:
-
如果
FGC/FGCT(Full GC 次数/时间)很高,可能是内存问题导致的 GC 频繁 -
如果
E(Eden区)一直接近 100%,可能是对象创建太快
6. 使用 jmap + MAT 分析内存(如果怀疑内存问题)
# 生成堆转储文件(生产环境慎用,会造成应用暂停)
jmap -dump:live,format=b,file=heap.hprof <java_pid>
# 或者只统计对象数量
jmap -histo:live <java_pid> | head -20
7. 使用 Arthas(推荐)- 实时诊断神器
# 启动 Arthas
java -jar arthas-boot.jar
# 选择目标 Java 进程
# dashboard # 查看实时系统面板
# thread -n 3 # 查看最忙的 3 个线程
# thread <tid> # 查看指定线程堆栈
# profiler start # 开始 CPU 性能分析
# profiler stop # 停止并生成火焰图
Arthas 关键命令
# 持续监控最忙的线程
thread -n 3 -i 1000
# 监控方法执行时间
trace com.example.xxxService expensiveMethod
# 生成火焰图(最直观定位热点方法)
profiler start
# 等待 30 秒...
profiler stop --format html
第四步:代码级定位 - 分析具体问题
8. 分析火焰图
生成的火焰图可以直观显示 CPU 时间消耗在哪些方法上:
-
宽平的栈顶:表示热点方法
-
从下往上:调用关系
-
点击具体栈帧:查看具体方法
9. 常见问题模式及代码示例
案例 1:死循环
// 问题代码
public void run() {
while (true) { // 缺少退出条件
// 处理业务
processData();
}
}
// 或者更隐蔽的
public void processQueue() {
while (!queue.isEmpty()) { // 如果queue被其他线程消费,可能永远不空
Data data = queue.poll();
handleData(data);
}
}
案例 2:算法复杂度高
// O(n^3) 的算法,数据量大时 CPU 爆炸
public void findTriplets(int[] nums) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
for (int k = j + 1; k < nums.length; k++) {
if (nums[i] + nums[j] + nums[k] == 0) {
// 处理结果
}
}
}
}
}
案例 4:频繁的序列化/反序列化
// 在循环内频繁创建 JSON 序列化器
public void processList(List<User> users) {
for (User user : users) {
// 每次循环都创建 ObjectMapper(昂贵操作)
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user); // CPU 密集型
sendToQueue(json);
}
}
// 优化:移到循环外
ObjectMapper mapper = new ObjectMapper(); // 单例
for (User user : users) {
String json = mapper.writeValueAsString(user);
sendToQueue(json);
}
第五步:系统级排查
10. 检查应用日志
# 查看错误日志
tail -f /path/to/app/log/error.log
# 搜索特定异常
grep -r "OutOfMemoryError" /path/to/logs/
grep -r "StackOverflowError" /path/to/logs/
grep -r "deadlock" /path/to/logs/
总结排查思路
-
宏观定位:
top→ 找到问题 PID -
线程分析:
top -H→ 找到问题 TID → 转换十六进制 -
堆栈分析:
jstack→ 关联 TID 与代码 -
工具深入:
Arthas/jstat/火焰图 → 定位热点方法 -
代码修复:根据分析结果修改问题代码
更多推荐



所有评论(0)