【Java】 服务器cpu过高如何排查和解决?

前言

对于互联网公司,线上CPU飙升的问题很常见(例如某个活动开始,流量突然飙升时),按照本文的步骤排查,基本1分钟即可搞定!特此整理排查方法一篇,供大家参考讨论提高。

线上系统突然运行缓慢,CPU飙升,甚至到100%,以及Full GC次数过多,接着就是各种报警:例如接口超时报警等。此时急需快速线上排查问题。

不管什么问题,既然是CPU飙升,肯定是查一下耗CPU的线程,然后看看GC。

一、常见能够引起CPU100%异常的情况都有哪些?

  1. Java 内存不够或者溢出导致GC overhead limit exceeded。

  2. 代码中互相竞争导致的死锁。

  3. 特别耗费计算资源的操作,比如正则匹配,Java中的正则匹配默认有回溯问题,复杂的正则匹配引起的CPU异常。

  4. 死循环引起的CPU高度密集计算。
    针对第1种,根据Oracle官方资料,GC overhead limit exceeded表示JVM一直在GC导致应用程序变慢,具体量化指标就是JVM执行垃圾回收花费超过98%的时间,但释放出的可用堆内存却少于2%,连续多次(一般5次)GC回收的内存都不足2%的情况下就会抛出此异常。

经过垃圾回收每次释放的内存都少于2%很容易又被新生对象填满,JVM快速进入下一次垃圾回收,无限循环,由此引起频繁的GC长期消耗我们服务器CPU资源,从而使CPU使用率达到100%

我们可以使用-XX:-UseGCOverheadLimit这个参数关闭GC overhead limit exceeded,但这样治标不治本,建议检查应用程序的内存使用是否合理以及是否需要增加堆内存。

二、服务器CPU使用率飙升异常,黄金4步排查法

  1. TOP命令找到占用CPU高的Java进程PID

    top

    在这里插入图片描述

  2. 根据进程ID找到占用CPU高的线程

    ps -mp pid -o THREAD,tid | sort -r

    在这里插入图片描述

  3. 将指定的线程ID输出为16进制格式

    printf “%xn” tid
    在这里插入图片描述

  4. 根据16进制格式的线程ID查找线程堆栈信息

jstack pid |grep tid -A 50

在这里插入图片描述

获取到线程堆栈信息就好办了,以上即是采用单线程模拟一个复杂的正则匹配的堆栈示例图,可以看得出线程都在指向regex.Pattern,在生产多线程环境下这个复杂正则匹配会导致CPU利用率奇高。

三、排查 CPU 故障的常用命令

  • top:Linux 命令。可以实时查看各个进程的 CPU 使用情况。也可以查看最近一段时间的 CPU 使用情况。默认按 CPU 使用率排序。
  • ps:Linux 命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前 CPU 使用情况。属于当前状态的采样数据。
  • jstack:Java 提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。
  • pstack:Linux 命令。可以查看某个进程的当前线程栈运行情况。

四、什么场景会造成 CPU 低而负载确很高呢?

负载总结为一句话就是:需要运行处理但又必须等待队列前的进程处理完成的进程个数。具体来说,也就是如下两种情况:

等待被授权予 CPU 运行权限的进程、等待磁盘 I/O 完成的进程。

CPU 低而负载高也就是说等待磁盘 I/O 完成的进程过多,就会导致队列长度过大,这样就体现到负载过大了,但实际是此时 CPU 被分配去执行别的任务或空闲,具体场景有如下几种:

  1. 数据库抖动,造成线程队列 hang 住,负载升高
  2. 磁盘读写请求过多就会导致大量 I/O 等待。CPU 的工作效率要高于磁盘,而进程在 CPU 上面运行需要访问磁盘文件,这个时候 CPU 会向内核发起调用文件的请求,让内核去磁盘取文件,这个时候会切换到其他进程或者空闲,这个任务就会转换为不可中断睡眠状态。当这种读写请求过多就会导致不可中断睡眠状态的进程过多,从而导致负载高,CPU 低的情况。
  3. 外接硬盘故障,常见有挂了 NFS,但是 NFS server 故障比如系统挂载了外接硬盘如 NFS 共享存储,经常会有大量的读写请求去访问 NFS 存储的文件,如果这个时候 NFS Server 故障,那么就会导致进程读写请求一直获取不到资源,从而进程一直是不可中断状态,造成负载很高。

五、监控发现线上机器内存占用率居高不下,如何分析进行优化?

  1. 使用top -p pid针对所要查的 pid 查看该进程的 CPU 和内存以及负载情况。
  2. jmap -histo:live [pid],然后分析具体的对象数目和占用内存大小,从而定位代码。
  3. jmap -dump:live,format=b,file=xxx.xxx [pid],然后利用 MAT 工具分析是否存在内存泄漏等等。