Java进程CPU飙升100%怎么办

教你使用多种工具定位CPU飙升问题函数

Posted by Jeremy Song on 2021-07-21
Estimated Reading Time 7 Minutes
Words 1.9k In Total
Viewed Times

CPU是计算机的大脑,所有的计算机进程都需要CPU来运行,那万一CPU炸了怎么办?

开个玩笑,这里指的并不是CPU真的“炸”了,而是CPU使用率过高,甚至一直100%居高不下。这个情况肯定可以断定,是某个进程将计算资源给霸占完了。如果这个情况发生在生产环境上,恐怕造成的后果不会那么轻轻松松吧。所以我们最好是在条件允许的情况下上线前做一次压测,将问题提前暴露出来。

好了,废话不说了。不管是生产环境、测试环境还是开发环境,如果发现了CPU使用率一直居高不下应该怎么定位到这个问题函数呢?下面就来告诉你怎么定位。当然,这里指的是Java进程的定位方法(●ˇ∀ˇ●)

定位思路

如何定位?

首先我们要明白一个java进程的运行,里面肯定是有一个或者多个线程在运行的(至少存在一个main线程),线程运行业务就需要CPU资源。所以我们就可以断定,肯定是这个问题进程中的某个线程有问题(当然,也可能不止一个)。

OK!到此为止,假设我们已经知道了是那些问题线程。接下来就是要分析,这个线程到底在干什么,怎么会用这么多的CPU资源。是不是聪明的你已经想到了?没错!一个线程运行在一个线程栈里面,我们只要拿到当前问题线程的线程栈不就知道这个线程现在干什么了吗!

所以总结一下,定位思路很容易,如下图所示:

接下来我举例分别介绍这几个步骤如何操作。

准备工作

为了您可以边看边学、边学边练,那就先创建一个如下内容的类吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.ArrayList;
import java.util.List;

public class CPULoadTester {

static class HeavyLoadTask implements Runnable {
@Override
public void run() {
while (true) {
int a = 1 + 1;
System.out.println("V " + a);
}
}
}

static class LightLoadTask implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("S");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) {
List<Thread> threadList = new ArrayList<>();
threadList.add(new Thread(new HeavyLoadTask(), "HeavyThread"));

for (int i = 0; i < 10; i++) {
threadList.add(new Thread(new LightLoadTask(), "LightThread-" + i));
}

threadList.stream().forEach(x -> x.start());
System.out.println("ALL THREAD STARTED!");
}
}

编译:

1
javac CPULoadTester.class

运行:

1
java CPULoadTester

如果您是Linux环境的话,屏幕上会打印出很多信息。你可以从新开一个新的连接或者使用下面的命令运行:

1
java CPULoadTester > /dev/null 2>&1 &

OK!我们的问题程序已经运行起来了,咱们接着往下看。

找到CPU使用率高的进程

Linux

您可以使用如下命令找到CPU使用率高的进程:

1
top -c


记住上面CPU使用率最高这个进程号(第一列的值),我们后面会用到。

Windows

您可以通过任务栏右键菜单或者使用组合键Ctrl + Shift + Esc打开任务管理器,在详细信息选项卡看到如下图类似的信息,点击CPU列使其从高到底排序。如果您的PC没有PID这一列,请依次点击菜单栏 选项 -> 设置默认选项卡 -> 进程 即可。

记住上面这个进程号,我们后面会用到。
PS:我的Windows上好像还没多高。哈哈,看来电脑还能多用两年。

找到CPU使用率高的线程

上面我们已经找到了CPU使用率高的进程,接下来告诉你如何找到问题线程。

Linux

如下命令列出指定进程的线程,${PID}就是上面我们找到的进程号:

1
top -Hp ${PID}


到此,我们已经找到了问题线程。记住这个线程号(第一列的值),我们后面会用到。

Windows

因为Windows的资源管理器看不到线程的情况,这里我们需要一个工具。我使用的是 Process Explorer v16.32,这是微软出的一款工具,您可以去 https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer 下载。

打开工具后可以,使用快捷键 Ctrl + F 打开搜索对话框,输入上面我们查找到的进程ID就找到定位到问题进程,如图:

双击当前定位到的进程,选择Threads 选项卡,点击CPU列进程排序,第一列的TID就是我们要找的进程号了,如图:

和Linux下一样,我们记住这个进程号(第一列TID的值)。

导出Java进程栈信息

这个对于Linux和Windows环境都是一样的,但是前提是你的环境必须是有JDK的,如果仅仅安装了JRE是不行的。使用如下命令导出栈信息:

1
jstack ${PID} > ./stackinfo.log

注:${PID}是我们上面查找到的进程ID。记住是进程ID,别搞错了。

分析栈信息找到问题代码

还记得上面的那个线程号吗?对!是用它的时候了。不过我们获取到的这个线程ID是十进制的,在使用之前必须把它转成16进制。你可以使用Windows上的计算器或者Linux下的命令 echo "obase=16;${TID}" | bc 或其他方式都可以。

使用记事本或者Linux的任何操作文本的命令打开上一步我们获取到的栈信息文件,然后在文本中查找上面得到的这个16进制数。如下图所示我们已经找到了问题函数(示例以Windows的为例,线程号11836转16进制为2e3c)。

结合上面准备的那段代码,是不是和你的预期一样呢。

使用Alibaba Arthas定位问题函数

到这,你已经完全掌握了如何定位CPU使用率高的问题函数。但是有人可能会说,这么多的操作我记不住啊,怎么办?

当然我是有办法给你的。还记我之前的文章讲过如何在Docker中使用Arthas的文章吗?没错,通过Arthas工具也可以定位出问题函数。没有使用过Arthas的同学或者想在Docker中使用Arthas的同学可以去官网查看,或者通过下面的连接查看我之前文章的讲解。

使用Arthas连接到进程

使用命令 java -jar arthas-boot.jar 选择上面查到的进程号,如下图:

查找问题线程

使用 dashboard 命令查找

使用 thread 命令查找


不管使用那两个命令,您都可以清楚的看见那个线程的CPU使用率高(个人推荐 dashboard ,因为可以减少瞬时值的影响),请记住这个线程号,就是第一列的数字

查看栈信息

使用命令 thread ${tid} 即可,${tid} 就是上面我们找到的线程ID。如下图:

怎么样是不是操作起来顺畅多了。(●ˇ∀ˇ●)

最后

授人以鱼不如授人以渔。说了这么多,其实最重要的还是第一部分将的定位思路。只要这个明白了,不管您用什么工具都是可以,万变不离其宗。


欢迎关注我的公众号 须弥零一,跟我一起学习IT知识。


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !