一、故障说明
昨晚突然收到线上服务器cpu报警,登录监控平台看了下发现cpu瞬间飙升到60%。第一反应就是使用top命令去查看,发现是一个java进程。于是立刻使用jps -l命令定位到该java进程,发现是一个后台服务,按理说晚上没什么人去使用后台服务,而且它也没有定时任务,查看了服务的日志都没有发现异常情况。
二、故障排查
既然是java进程导致的cpu飙升,于是我使用jstat -gcutil [pid] 5000命令去查看GC情况,结果发现Eden区的垃圾回收非常频繁,然后使用jmap -heap [pid]查看了当前java进程内存配置,Eden区分配了600M的内存,什么样的操作导致600M的内存都不够使用呢?于是我把Eden升级到了800M,结果还是一样CPU并没有降下来。看来一味增加内存是无法解决问题了,只能乖乖的去定位故障了。
三、故障定位
我先是dump出当时的堆栈信息,然后使用MAT去分析,结果并没有发现什么有价值的信息。于是我就想还是要去查看堆栈内的实时数据来定位问题。
这里使用的工具是Jprofiler,功能强大,界面友好,版本是9.2(大家可以去使用更新的版本,我这里使用9.2是因为网上找不到高版本的破解码,当前找不到破解码也可以试用)。我们都知道图表最能直观反映问题,而我们的线上服务器是linux,所以必须把指标抓取到本地的window服务器上这样才能用图表的形式直观展示。因此这里需要Jprofiler服务端(安装到linux)和Jprofiler客户端(安装在window),下面是对Jprofiler的安装、使用过程。
-
Jprofiler下载
Jprofiler9.2下载链接
-
Jprofiler在window下安装
window版本我们直接在本地安装即可 -
Jprofiler在linux下的安装
linux版上传到远程服务器/opt目录下,然后tar -zxvf 解压(上传目录不一定是/opt,大家可以自行更改) -
收集服务端信息
然后在想要监控的服务启动脚本中增加下面这段代码(代码中的路径需要结合自身情况做修改,端口随便指定一个即可)-agentpath:/opt/jprofiler9/bin/linux-x64/libjprofilerti.so=port=23498,nowait
// 修改完启动脚本后启动服务,然后查看下上面的端口是否启动 netstat -lnp|grep 23498 // 我们的监控信息需要通过23498端口传到客户端,因此需要在防火墙中开启该端口 firewall-cmd --zone=public --add-port=23498/tcp --permanent // 重启防火墙 systemctl restart firewalld // 查看端口是否生效 firewall-cmd --list-ports
(如果大家使用的阿里云等第三方云服务,还需要自行修改安全组配置)
-
客户端展示
- 问题定位
首先我们第一眼看到的就是下面的图片,图片中有Memory、GC、Classes、CPU这几个指标。其中我们重点关注Memory、GC、CPU。可以看到Memory几秒钟就下降一次,那么是什么东西能顾在几秒钟内占据几百兆的内存呢?
我们知道Eden区属于新生代,新生代里存放的都是朝生夕死的对象,于是我们来监控下对象,看看哪些对象占用的内存最大,通过监控我发现占用内存最大的就是byte[],那么byte[]对象又是哪里来的呢?
继续分析定位到一个方法:java.awt.image.RenderedImage.getData,看来byte[]就是这个方法生成的。
打开方法的调用栈,发现原来了是我们的一个图片转webp的工具类用到了这个方法。
四、总结
综上,从发现问题到定位问题我们经过了下面的步骤
- 定位导致cpu飙升的服务
- 查看GC情况,判断是否是因为GC频繁导致的CPU飙升
- 查看服务内存配置,观察内存配置是否合理
- 升级服务内存配置看是否能够降低GC频率,进而降低CPU
- 借助工具分析内存实时情况
- 定位占用内存较大的对象
- 找到生成该对象的方法调用栈,进而定位具体有问题的代码