线上Java应用导致CPU占用率过高的排查和解决方案

在我们日常开发中,如果Java程序在运行中占用大量的CPU资源,此时就会导致系统性能急剧下降、系统的响应变慢甚至会导致系统由于大量请求打过来而承受不住进而崩溃。所以CPU占用率一致是我们所要关心的问题。

如果系统中CPU占用率过高我们如何定位呢?下面针对这个定位的过程做一个详细的介绍。

写一个会让CPU资源占满的程序,如下:

public class JVMController { 
    @GetMapping("/loop") 
    public String loop() { 
        while (true) { 
          log.info("欢迎关注:龙虾编程"); 
        } 
        return "欢迎关注:龙虾编程"; 
    } 
}

1、定位占满资源的CPU进程

# top命令可以找到CPU消耗最大的进程PID 
top

备注:Linux下CPU的占用比中,100%代表一个核被占满,如果4核最高可以达到400%。

2、定位进程下的占用CPU过高的线程

# top -Hp <p>   
# H:显示线程  p:根据CPU使用百分比大小进行排序 
top -Hp 4843

3、进程转十六进制

# printf  '%xn'  <p> 
printf '%xn' 4864

4、通过jstack命令分析出现问题的代码

# jstack <p> |grep <p> -A <自定义打印代码的行数> 
jstack 4843 |grep 1300 -A 30

这里如果觉得堆栈信息看起来不方便,我们可以导出堆栈信息的dump文件,然后使用工具(如VisualVM)来分析问题找原因。

通过这几步的分析我们定位到了我们出现问题问题的代码,在这个代码中我们确实是写了一个无限循环到CPU资源被占满。

常见的CPU资源占用率过高的情况:

(1)无限循环或者死循环(如上案例的死循环)

(2)程序中使用了不合理的算法和数据结构;不合理的算法和数据结构会导致程序的运行时间变长,从而增加CPU使用率。

(3)频繁的IO;如果频繁进行IO操作会导致CPU长时间等待IO结果,从而造成CPU使用率高。

(4)内存问题;内存泄漏导致内存占用过高的情况,会触发JVM的频繁FullGC。

(5)异常处理不当;在Java中异常处理不当可能会导致CPU占用过高,因为异常处理可能会涉及堆栈跟踪等操作。

(6)大量字符串操作; J ava中的字符串是不可变的,如果程序中进行大量的字符串操作(如拼接、替换),会导致大量的临时对象的创建和销毁,从而增加CPU使用率。

(7)线程自身问题;在多线程中,线程可能会争夺共享资源导致死锁或者阻塞等问题导致CPU占用过高。

常见的CPU占用率过高的解决办法:

(1)优化代码;优化算法和数据结构、在代码的循环中增加一个次数限制来防止出现死循环问题、优化IO操作等等。

(2)线程管理;确保多线程下程序不会出现竞争资源导致的死锁。

(3)内存管理;写代码的时候注意代码的内存释放(如ThreadLocal,它如果没有注意就喜欢内存泄露)。

(4)JVM的参数调整;通过调整JVM参数来减少JVM的垃圾收回次数。

9