线上 CPU 爆满如何排查

模拟代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ThreadDemo {

static Map<String, String> map = new HashMap<>();

public static class AddThread implements Runnable {

int start = 0;
public AddThread(int start) {
this.start = start;
}
@Override
public void run() {
//死循环,模拟CPU占用过高场景
while (true) {
for (int i = start; i < 100000; i += 4) {
map.put(Integer.toString(i), Integer.toBinaryString(i));
}
}
}
}
public static void main(String[] args) throws InterruptedException {
//线程并发对 HashMap 进行 put 操作 如果一切正常,则得到 map.size() 为100000

//可能的结果:
//1. 程序正常,结果为100000
//2. 程序正常,结果小于100000
Thread thread1 = new Thread(new AddThread(0), "myTask-1");
Thread thread2 = new Thread(new AddThread(1), "myTask-2");
Thread thread3 = new Thread(new AddThread(2), "myTask-3");
Thread thread4 = new Thread(new AddThread(3), "myTask-4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
System.out.println(map.size());
}

}

排查

  1. top 查看 cpu 的使用情况

    image-20210820142321928

    发现 303 的 java 进程使用了 300+ 的 cpu

  2. 使用 top -H -p 303,查看子进程-线程的使用情况

    image-20210820142456208

    发现第一个 pid 为 319 的线程消耗 cpu 比较多

  3. 使用 jstack 303 查看 java 线程堆栈信息,因为 jsatck 里面的线程 id 是 16进制的,所以吧 319 专程 16进制为 13f

    image-20210820142743238

    可以发现就是 ThreadDemo 的代码消耗了 cpu,定位结束

其他扩展

  1. 如果追踪到是大量的 gc 线程,那可以通过 jmap 查看堆栈使用情况,jstat 查看 gc 统计,定位是否有大量的可疑对象。
  2. 还可以通过 增加启动参数 -XX:+HeapDumpOnOutOfMemoryError,打印程序内存溢出,然后导入到 mat 工具里面分析