Java线上CPU飙高问题排查全指南
炸了!Java线上CPU突然飙到99%?老司机教你3步搞定排查!
各位技术大佬们,晚上好呀!我是你们的老朋友——服务端技术精选的老司机。
今天咱们聊点刺激的:凌晨三点,你正抱着枕头做着美梦,突然手机“嗡”地一声震动。眯着眼睛一看,监控告警炸了:线上服务CPU使用率99%,请求延迟从50ms飙到5s,用户已经开始在群里吐槽。
这时候你怎么办?是手忙脚乱登录服务器一顿瞎操作,还是胸有成竹按套路排查?别慌,今天老司机就给你一套“CPU飙高排查连招”,3步搞定问题,让你下次遇到这种情况,能淡定地说一句:“小场面,常规操作~”
第一步:定位“凶手”进程——找到那个吃CPU的家伙
首先,咱们得知道是哪个进程在疯狂吃CPU。登录服务器后,直接敲一个命令:
$ top
这个命令会列出当前系统中CPU使用率最高的进程。你会看到类似这样的输出:
···
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 root 20 0 3270468 589428 18348 R 99.7 7.3 0:34.21 java
···
看到没?PID为1234的java进程,CPU使用率已经接近100%了,这就是咱们要找的“凶手”!记下来这个PID,接下来咱们要深入这个进程内部看看。
第二步:定位“帮凶”线程——找到具体哪个线程在搞事情
进程找到了,但一个Java进程里可能有几十个甚至上百个线程,到底是哪个线程在疯狂消耗CPU呢?这时候咱们需要用到另一个命令:
$ top -Hp 1234 # 1234是刚才找到的进程PID
这个命令会列出该进程下所有线程的CPU使用情况。同样,找到那个CPU使用率最高的线程,比如:
···
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1256 root 20 0 3270468 589428 18348 R 95.2 7.3 0:30.12 java
···
这里的PID 1256就是消耗CPU最多的线程ID。不过,Java线程栈里的线程ID是十六进制的,所以咱们需要把这个十进制的1256转换成十六进制。可以用printf命令:
$ printf "%x\n" 1256
4e8
现在,咱们得到了线程ID的十六进制表示:4e8。
第三步:分析线程栈——揪出问题代码
接下来,咱们需要导出Java进程的线程栈,看看这个线程到底在执行什么代码。使用jstack命令:
$ jstack 1234 > thread_dump.txt
然后,打开这个thread_dump.txt文件,搜索十六进制的线程ID 4e8(注意,可能需要去掉前面的0,或者加上0x前缀,具体看文件格式)。你会找到类似这样的内容:
···
"Thread-1" prio=10 tid=0x00007f8a1c34b000 nid=0x4e8 runnable [0x00007f8a14a6e000]
java.lang.Thread.State: RUNNABLE
at com.example.service.UserService.calculateScore(UserService.java:123)
at com.example.service.UserService.processUserData(UserService.java:89)
at com.example.controller.UserController.handleRequest(UserController.java:45)
...
看到没?这个线程正在执行UserService.java文件的calculateScore方法,行号是123。这时候,咱们就可以去查看这个方法的代码了。
假设咱们查看代码后发现,calculateScore方法里有一个死循环,或者是一个非常耗时的计算逻辑,没有正确处理边界条件,导致CPU一直高负载运行。比如:
public int calculateScore(User user) {
int score = 0;
while (true) { // 这里是个死循环!
score += user.getPoints();
if (score > 10000) {
break; // 但是条件永远不满足,导致无限循环
}
}
return score;
}
找到了问题代码,剩下的就简单了:修复代码,重新部署,问题解决!
实战案例:从CPU飙高到问题解决的完整过程
去年,我们线上有个服务突然CPU飙到99%。按照上面的步骤排查,发现是一个定时任务在处理数据时,因为数据库查询条件不正确,导致返回了100万条数据,然后代码里又对这100万条数据做了多层循环处理,直接把CPU干到爆表。
定位到问题后,我们修改了数据库查询条件,添加了分页处理,并且优化了循环逻辑,CPU使用率马上降到了10%以下。
预防措施:让CPU飙高问题远离你的系统
- 添加监控告警:设置CPU使用率阈值(比如80%),一旦超过就告警,不要等到用户投诉才发现问题。
- 定期代码Review:重点关注循环、递归、大数量数据处理等容易导致CPU高负载的代码。
- 性能测试:上线前用压测工具模拟高并发场景,提前发现性能瓶颈。
- 使用线程池:合理设置线程池参数,避免创建过多线程导致CPU上下文切换频繁。
- 日志记录:在关键方法中添加日志,方便问题排查。
总结
Java线上CPU飙高问题并不可怕,只要掌握了正确的排查方法,就能快速定位并解决问题。记住这3步:定位进程→定位线程→分析线程栈。
最后,老司机再提醒一句:排查问题时一定要冷静,不要盲目重启服务(虽然有时候重启能解决问题,但会丢失现场,不利于后续分析)。按照套路来,问题总能解决的!
如果觉得这篇文章有用,欢迎分享给身边的小伙伴,也欢迎在评论区留言讨论你遇到过的CPU飙高问题~
咱们下期再见!