Arthas是阿里开源的 Java 诊断工具,相比 JDK 内置的诊断工具,要更人性化,并且功能强大,可以实现许多问题的一键定位,而且可以一键反编译类查看源码,甚至是直接进行生产代码热修复,实现在一个工具内快速定位和修复问题的一站式服务。今天,我们就来学习一下如何使用Arthas 定位一个 CPU 使用高的问题。
首先,下载并启动 Arthas:
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
启动后,直接找到我们要排查的 JVM 进程,然后可以看到 Arthas 附加进程成功:
$ java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.6.6
[INFO] Process 12024 already using port 3658
[INFO] Process 12024 already using port 8563
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 12024 fcp.troubleshootingtools.arthas.HighCPUApplication
[2]: 7476
[3]: 18716 C:\Users\fuche\Desktop\MemoryAnalyzer\mat\\plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
[4]: 23100 org.jetbrains.jps.cmdline.Launcher
1
[INFO] arthas home: C:\Users\fuche\.arthas\lib\3.7.0\arthas
[INFO] The target process already listen port 3658, skip attach.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 3.7.0
main_class
pid 12024
time 2023-08-16 19:56:06
输出 help 命令,可以看到所有支持的命令列表。也可以通过官方文档来查看这些命令的完整介绍。
1、使用dashboard展示所有线程、内存、GC 等情况,其输出如下:
从dashboard中可以看出,CPU高并不是GC引起的,而是由于ForkJoinPool引起的,ForkJoinPool是并发流默认使用的线程池,因此问题可能是出现在某段并发流的代码上。
2、使用thread -n查看最繁忙的8个线程栈
thread -n 8
可以看到这些线程是因为正在处理MD5操作,所以占用了大量的CPU资源。如果我们希望分析出代码中哪些逻辑可能会执行这个操作,所以需要从方法栈上找出我们自己写的类,并重点关注。也就是我们HighCPUApplication类的doTask方法
3、使用Jad命令对HighCPUApplication 类进行反编译
可以看到调用路径是 main->task()->doTask(),当 doTask 方法接收到的 int 参数等于某个常量的时候,会进行 1 万次的 MD5 操作,这就是耗费 CPU 的来源。那么,这个魔法值到底是多少呢?
我们可以使用jad查看User类来知道这个魔法值是多少,但实际业务场景中判断逻辑不可能这么直白,在这种情况下如果我们还想知道doTask会慢在什么入参上怎么办呢?
4、使用watch命令监控耗时超过100ms的doTask方法的入参,并且输出入参,展开 2 层入参参数
watch fcp.troubleshootingtools.arthas.HighCPUApplication doTask '{params}' '#cost>100' -x 2
可以看到,所有耗时较久的 doTask 方法的入参都是 0,意味着 User.ADMN_ID 常量应该是 0。
5、使用ognl命令执行一个表达式,验证User 类的 ADMIN_ID 静态字段是否为0
[arthas@29904]$ ognl '@fcp.troubleshootingtools.arthas.User@ADMIN_ID'
ognl '@fcp.troubleshootingtools.arthas.User@ADMIN_ID'
@Integer[0]
最后总结一下:
- 我们通过dashboard + thread可以快速定位出消耗CPU最多的线程以及方法栈
- 然后通过jad反编译相关代码来确定原因
- 此外我们可以借助watch观察当出现慢请求时,方法的入参是什么样的
- 以及使用ognl可以去查看一些字段的值
最后呢,可以到代码地址中下载相关代码,然后本地实践一下。以及本篇文章的内容实际上是学习自极客时间的《Java业务开发常见错误100例》这是一个实战性比较强的专栏,推荐大家也可以去看看
如果我们没有arthues或不允许使用arthues,可以参考这篇文章线上java程序CPU飙升问题排查