如何快速定位线上CPU飙高问题

传统方式排查

模拟CPU飙高的场景

Linux环境上运行测试Demo,模拟CPU飙高的场景

@RequestMapping("testCpu")
public String testCpuCpuSurgesOperation(){
    // 模拟占用CPU资源操作
    while (true){
        
    }
}
top命令查找占用CPU高的进程

项目启动后,调用testCpu接口,通过top查看进程占用cpu情况,由下图可知进程号为15565的进程占用100.3%的CPU资源
image.png

根据进程号找到占用CPU高的线程

top -Hp -15565 找到进程中占用CPU高的线程
image.png

线程的转储信息输出到文件中

jstack -l 15565 >> stackdump.txt 用于生成线程转储信息的命令,并将其追加到名为 stackdump.txt 的文件中。

  • jstack 是一个 Java 命令行工具,用于生成 JVM中所有线程的转储信息。
  • -l 选项表示生成详细的线程转储信息,包括锁信息。
  • 15565 是要生成线程转储的进程ID。
  • >> 是重定向操作符,用于将输出追加到指定的文件中。

image.png
image.png

线程id转为为16进制

使用printf “%x
” 线程id将异常线程id转化为16进制
image.png

线程转储文件中找到对应的线程

打开线程转储文件(stackdump.txt),通过上面输出的16进制的线程id(3cf0)找到对应线程的堆栈信息,这时我们就可以定位到CPU飙高代码的具体位置。
image.png
image.png

arthas工具排查

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
官网:https://arthas.aliyun.com/doc/

arthas能为我们做什么?

image.png
下面我将介绍使用arthas工具排查CPU飙高和死锁的问题

下载并启动arthas

从官网下载完成后上传到服务器,使用 java -jar arthas-boot.jar 启动arthas
image.png

选择需要监控的进程

启动时arthas会把所有的运行的java进程列举出来,需要选择我们去需要监视的java进程,进程前有对应的标识选择数字,因为我的服务器只有一个正在运行的java进程,如下图所示只需选择1即可。
image.png

dashboard

dashboard:实时查看系统的运行状况。输入dashboard后能看到占用CPU 99.25%的线程。ctrl + c可以退出查看。
image.png

根据线程id找到代码块

thread:查看当前 JVM 的线程堆栈信息。从上图可知占用CPU大量资源的线程id为17,输出 thread 17就可以定位到CPU飙高代码的具体位置。

参数名称 参数说明
thread -all 显示所有的线程
thread -id 显示指定线程的运行堆栈;
thread -b 找出当前阻塞其他线程的线程
thread -n 3 指定最忙的前 N 个线程并打印堆栈

image.png

模拟死锁的场景
   @RequestMapping("testDeadLock")
    public void testDeadLockOperation(){
        new Thread(()->{
            // 线程A获取到资源A
            synchronized (sourceA){
                System.out.println("Get resource A");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                // 尝试获取B资源
                synchronized (sourceB){
                    System.out.println("try get resource B");
                }
            }

        },"A").start();

        new Thread(() ->{
            synchronized (sourceB){
                System.out.println("Get resource B");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                // 尝试获取A资源
                synchronized (sourceA){
                    System.out.println("try get resource A");
                }
            }


        },"B").start();
    }
查询阻塞的线程

只需执行“thread -b”命令,找出当前阻塞其他线程的线程
image.png

总结:
  1. 通过对比以上两种排查问题的方式,arthas在定位CPU飙高和死锁的场景下更加方便和高效。
  2. 以上内容只是简单的介绍arthas的线程分析功能,它还有一些更加强大的功能,比如:内存分析、方法追踪、字节码注入等等…