前言

不止一次写过CPU飙高的问题排查(当时并不仅限于Java),不过由于各种原因这个小破站换了N多个虚拟主机、服务器、域名等中间丢失了很多珍贵的原创博客。最近闲暇的时候看了一下仅存的2018年写的一篇垃圾水文😂当时没有图文并茂的去展现整个流程,今天重新以图文的方式记录一遍。

实战准备

首先使用micronaut快速生成一个项目:mn create-app example.micronaut.complete

⚠️ 这个Micronaut是Java微服务框架的一种,可类比springboot,具体的你可以上网找资料看一下。这里不展开讨论,这里使用它的原因是因为它比较快。。。仅此而已👌。 当然了,你也可以用springboot或其他你喜欢的方式生成一个死循环接口。

然后创建controller类,写类似下面这种死循环的请求接口:

package example.micronaut;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;

@Controller("/test")
public class DemoController {
@Get
@Produces(MediaType.TEXT_PLAIN)
public String index() {
for (int i = 0; i > -1 ; i++) { // 这里 i > -1 始终为true
System.out.println(i * i++); // 这里随便写个计算 并打印输出
}
return "ok";
}
}

最后打个jar包放到Linux服务器上运行,在浏览器中请求test接口去触发死循环。

终端会持续输出计算结果,如果该Linux服务配置较低不出意外的话现在已经比较卡了。

1、首先祭出大杀器:top

Linux中top命令用于实时显示 process 的动态。(引用自runoob)

直接执行top命令然后shift + p按照%CPU去排序

这时我们发现PID605108的Java进程CPU占用开始飙高。

2、其次再利用top:top -p PID -H

这一步我们来查询这个Java进程中哪个线程的CPU占用在飙高。

这个地方显示PID605162的线程CPU在飙高。

3、再次祭出另外一个大杀器:jstack

使用jstack PID > 1.txt将堆栈信息保存到1.txt。

  • 注意这里的PID是Java进程的PID而不是线程的。
  • 顺带着将605162转换为16进制(最后一行root前的93bea即为转换结果)。
root@ubuntu:~# jstack 605108 > 1.txt
root@ubuntu:~# printf "%x" 605162
93bearoot@ubuntu:~#

⚠️ 这里的”%x”是格式化为16进制的意思,你可以通过任何你喜欢的方式转换,甚至电脑上的程序员计算器都行。

4、最后分析文件找出”水鬼”

vim打开文件并搜索93bea

我们发现下面这么一行描述。

at example.micronaut.DemoController.index(DemoController.java:14)

在example.micronaut.DemoController类中index方法第14行这么一个信息。

那么我们去找一下刚才我们写的方法,看看问题是不是在14附近。

System.out.println(i * i++);

经过代码中的查找我们发现确实我们当时创建的死循环执行就是在这里。

✖️ OK,这就是今天带来的四步排查法。是不是很简单?