1 场景
通过linux的top命令
和jdk的jstack
命令来排查当前系统CPU占用最多的线程。
2 步骤
主要步骤如下图:
2.1 测试代码
SpringBoot中写一个死循环
// package com.sa.jvm.admin.controller;
/**
* 死循环测试
*/
@Controller
@RequestMapping("/infiniteLoop")
public class PowerController {
@RequestMapping("test")
public String test() {
while (1 == 1) {
System.out.println(new Random().nextInt(1000));
}
}
}
当前访问此请求路径(/infiniteLoop/test)时,CPU将飙升。
发起请求后,控制台将无限输出1000以内的随机数,如下:
http://192.168.0.11:8080/infiniteLoop/test
2.2 排查问题
(1)查找CPU占用最高的进程
linux系统中输入如下命令,查看CPU占用最高的进程
:
top -c
如图所示,PIP为3147
的java进程的CPU占比最高。此处已经定位到有问题的进程
PID为3147。
top -c含义:
每隔5秒显式进程的资源占用情况,并显示进程的命令行参数(默认只有进程名)
另外TOP模式下,常用交互命令如下:
命令 | 说明 |
---|---|
P | 以 CPU 占用率大小的顺序排列进程列表 |
M | 以内存占用率大小的顺序排列进程列表 |
(2)查看进程中CPU占比最高的线程
已经找到CPU占比最高的进程,ID为3147
,继续查找此进程对应的线程信息
。
linux系统中输入如下命令,查找指定进程中,CPU占用最高的线程
:
top -H -p 3147
如图所示,该进程中CPU占比最高的进程的PID为:3168
,此处已经定位到有问题的线程
PID为3168。
linux中通过如下命令,将线程的PID转为小写16进制。
printf '%x\n' 3168
得到16进制的线程ID为:c60
(3)导出进程堆栈信息
此处我们得到有问题的进程为3147
,有问题的线程ID的16进制为c60
。
先使用jdk的jstack
命令根据线程PID
导出对应的线程栈信息。
jstack 3147
此命令会输出此java进程中的所有的线程栈
信息,我们通过有问题的线程的16进制PID
查找问题线程对应的栈信息。
可以使用如下命令进行线程栈信息过滤
jstack 3147 | grep c60 -A 50
或j将线程栈信息重定向到文件中,再进行查询:
jstack 3147 > jstack.log
得到查询结果如下:
如图所示,此CPU占用主要为输出打印内容导致的CPU占用,有问题的代码为:
at com.sa.jvm.admin.controller.PowerController.test(PowerController.java:18)
我们查看源码,进行问题定位:
有问题的代码第18行如下,此时已经定位到问题的代码,和我们模拟的死循环代码一致:
3 服务器CPU使用率高的情况
服务器CPU使用率高的情况,除了以上的业务代码外。此处对可能出现的原因进行分析。
3.1 CAS
使用AtomicInteger
、CountdownLatch
等基于CAS
的相关类时或者自己实现CAS时,当值修改失败
时,会一直高速循环
。
解决方案:
(1)限制重试次数
(2)间隔一段时间后重试
3.2 程序死循环
如本文的案例,当业务代码出现死循环无法跳出
的情况时,会长时间大量占用CPU。
解决方案:
在代码级别进行控制,保证代码质量和健壮性,流程代码需有退出循环的条件
。
3.3 tomcat并发量太高
tomcat默认线程池数量200,一般不会更改为太高,大多数的请求都是快速响应
。
并发请求数暴增,超过tomcat的负载能力
后,web容器中会堆积大量的请求,会占用过多的CPU资源导致响应慢和卡顿。
解决方案:
(1)限流、进行请求数量的控制
(2)负载均衡
(3)处理相应慢的请求业务代码......
3.4 服务器被攻击
当前服务器的端口被攻击
后(如redis的6379端口)。
如何排查:
查看服务器网路的带宽情况和非法程序
解决方案:
(1)更改程序的通用端口为其他端口,防止被攻击
(2)运行环境,尽量使用同一的局域网,只对外部暴露可供访问的程序端口。如使用阿里云等环境,需尽量使用同一的云环境,环境中的服务可通过局域网的方式访问。
3.5 java虚拟机GC频繁
当java虚拟机频繁GC
时,根据本文的方法,查找到的CPU占用高的线程是JVM的GC线程
,没有对应的业务代码,无法定位业务代码
。
解决方案:
(1)加大堆内存(临时解决
)
(2)生成堆dump,通过MAT或jprofile排查大对象产生的代码。一般生产环境,不会允许进行堆dump
,此种情况,可以考虑使用本文的方法,查找仅CPU占用率前几的线程栈信息
或者CPU执行时间前几的线程栈信息
。