排查线上 java 进程CPU 飙升的问题
背景
某天晚上突然在工作群里看到服务响应变慢,于是急忙登录服务器看一下应用状态。如下图应用进程的状态一直徘徊在100%左右,但是一个台机器的服务cpu占用率极低。
根据网上资料
https://blog.csdn.net/qq_33404395/article/details/86242263?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
经过一番折腾最后定位到问题的原因,在极端的情况下系统陷入了死循环,问题伪代码如下
int i = 0;
while(i < 10){
Integer result = xxxx; //执行业务代码,获取返回结果
if(result != null){
i ++;
}
}
至此问题已经很明显,由于业务迭代变更,执行业务代码有可能变成null,所以陷入了死循环。
以下是自己在本机上写了个简单的demo,做出的一些总结。代码写了2个线程,其中一个线程陷入死循环状态。 由于本人是mac电脑 无法使用 top -H的命令(google了很久没找到对应的命令,无法定位进程里面的线程ID),因此使用docker作为虚拟机进行测试
package com.andy.jdk8.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 模拟 cpu 飙升 test 类
*/
public class CpuTest {
public static void main(String[] args) {
boolean isTrue = true;
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("dead loop");
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
for(int i = 0;i < 100000;i++){
try {
System.out.println("sleep loop");
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}
}
}
});
}
}
步骤如下:
1、运行容器,将本机的class文件挂载到docker容器的/data目录下
docker run -itd --name java-cpu -v /Users/andy/tool/structure/structure/java/target/classes:/data java /bin/bash
2、进入容器,启动应用
docker exec -it java-cpu /bin/bash
cd /data
nohup java com.andy.jdk8.pool.CpuTest &
nohup 命令将程序作为后台进程运行
3、运行jcmd 命令,查看当前机器有多少java进行运行,如下图,前面的125就是进程id
4、找到进程中 占用资源率最高的线程
输入 top 125 ,然后按住 shift + h
4、可以看出140 这个线程ID,占用cpu的资源比较高,并将之转换成16进制
printf "%x\n" 140
5、定位线程代码,查看堆栈信息
jstack 125 |grep 8c -A 30
很明显问题出现在程序的第19行
6、我将排查的步骤写成了一个shell 脚本,以后直接运行就可以了。执行脚本的时候 代码应用名称,该脚本会查找应用进程里面占用资源cpu资源最高的线程堆栈,并输出到日志文件
pid=$(jcmd | grep $1 | awk '{print $1}')
echo "java process pid = $pid"
threadid=$(top -Hp $pid -d 2 -n 1 -b | awk 'NR==8{print $1}')
threadid0x=$(printf "%x\n" $threadid)
jstack $pid |grep $threadid0x -A 30 > log.txt
echo "execute success"
7、将文件内容名称成test.sh,执行 ./test.sh CpuTest . 执行的结果跟上面一致