Arthas 快速入门
封面来源:本文封面来源于 Arthas 官网,如有侵权,请联系删除。
官方文档:arthas
本文参考:Java 诊断神器Arthas真有那么香?它到底能解决什么问题 | Arthas 教程实操 | 线上问题排查思路和手段
测试代码:springboot-study/arthas-demo at master · mofan212/springboot-study
1. Arthas 的简介
今天同事问我会不会使用 Arthas 来查看接口的耗时,奈何本人才蔽识浅,仅仅使用过它的 jad
命令来反编译类文件。突然想起曾经收藏过一个关于如何使用 Arthas 的视频,那就利用这个今晚的时间学习下吧。
简介
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 Load、内存、GC、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
诸如此类的工具就和 Mac Book 一样,当你在考虑要不要买 Mac Book 时,那就不要购买;当你不知道 Arthas 有什么用,那就不用学。
能解决什么问题
Arthas 是阿里巴巴出品的线上 JVM 监控诊断利器,它适用于:
-
有没有一个全局 JVM 运行时监控?能够显示 CPU、线程、内存、堆栈等信息
-
CPU 飙高是什么原因造成的?
-
接口没反应、卡住了,是不是死锁了?
-
CTO 说你们这个接口太慢了,要优化一下,如何准确找出耗时的代码?
-
写的代码没有执行,是部署的分支不对,还是压根没提交?
-
线上有一个低级错误,改起来很简单,能不能在不重启应用的情况下,进行类替换,达到热部署?
2. 快速入门
2.1 安装与启动
进入 下载 | arthas 页面,可以从 Maven 仓库下载,也可以前往 Github Releases 页下载,下载完成后,解压文件。
而在 MacOS / Linux 环境下,可以直接执行以下命令下载:
1 | wget https://arthas.aliyun.com/arthas-boot.jar |
在启动 Arthas 前,需要有一个 JVM 进程。先运行 main()
方法:
1 |
|
在有 arthas-boot.jar
的目录下,使用 java -jar
的方式启动:
1 | java -jar arthas-boot.jar |
运行后会列出所有存在的 Java 进程,找到需要连接的进程:
之后输入目标进程对应的序号,当界面成功显示 Arthars 的 Banner 时,证明连接成功:
2.2 基本命令的使用
help
查看当前 Arthas 版本支持的指令,或查看具体指令的使用说明。
或者在某一命令后使用 -h
选项,查看该命令的用法和示例,比如:
1 | dashboard -h |
根据示例,输入 dashboard
,查看 JVM 运行时监控:
按 Ctrl + C
退出。
thread
thread
命令可以查看当前线程信息、线程的堆栈。
运行下列代码:
1 |
|
利用 Arthas 连接到对应的进程,执行 thread
命令:
目标线程的序号是 11
,运行下列命令查看 11
号线程对应的信息:
jad
现在需要打印出一些好习惯:多读书,多看报,少吃零食,多睡觉。
运行下列代码:
1 |
|
控制台打印出:
多读书,多看报
只打印了前半句,这是怎么回事呢?
使用 jad
跟上目标类的全限定名称,反编译目标类:
上述反编译结果有两个问题:
- 在
doSomething()
方法中只调用了readAndSleep()
方法,没有其他内容,导致只打印了前半句; readAndSleep()
方法中的中文乱码。
针对第一个问题,查看源码后发现代码被注释:
1 | public class GoodHabit { |
放开注释,重新运行程序,控制台打印出:
多读书,多看报 少吃零食,多睡觉
第二个问题的解决则是要在启动 Arthas 时设置 Arthas 向控制台输出内容使用的默认编码:
1 | java -Dfile.encoding=UTF-8 -jar arthas-boot.jar |
2.3 方法的监测
首先推荐一个 IDEA 插件:Arthas Idea,利用该插件可以很方便地生成 Arthas 命令。
watch
watch
命令用于监测方法执行数据。
运行以下代码,循环打印 Car
的信息:
1 | private Car getCar(String carName, BigDecimal carPrice) { |
此时需要监测 getCar()
方法,鼠标右击目标方法,选择 Arthas Command
,然后再选择 Watch
生成 Arthas 命令:
最终生成的命令如下:
1 | watch indi.mofan.ArthasDemo getCar '{params,returnObj,throwExp}' -n 5 -x 3 |
这表示:监测 indi.mofan.ArthasDemo
类中的 getCar()
方法,获取方法调用时使用的参数、返回值和异常信息,共监测 5 次,输出的对象属性遍历深度为 3。
如果跟踪的字段或对象过大,会导致输出的内容太多,甚至可能因为控制台的限制而丢失信息。此时可以尝试将 watch
命令的输出重定向到文件中,比如:
1 | watch indi.mofan.ArthasDemo getCar '{params,returnObj}' -n 5 -x 3 > /path/to/output.txt |
trace
trace
命令用于获取方法内部调用路径,并输出方法路径上的每个节点上耗时。
利用 Arthas Idea 插件生成相应命令:
1 | trace indi.mofan.ArthasDemo getCar -n 5 --skipJDKMethod false |
这表示:监测 5 次 indi.mofan.ArthasDemo
类中的 getCar()
方法,并且不跳过 JDK 中的方法。
执行 trace
命令后,耗时占比最高的部分会高亮显示。
stack
stack
命令用于输出当前方法被调用的调用路径。
利用 Arthas Idea 插件生成相应命令:
1 | stack indi.mofan.ArthasDemo getCar -n 5 |
这表示:监测 5 次 indi.mofan.ArthasDemo
类中的 getCar()
方法的被调用路径。
monitor
monitor
命令用于方法执行监控。
利用 Arthas Idea 插件生成相应命令:
1 | monitor indi.mofan.ArthasDemo getCar -n 10 --cycle 10 |
这表示:循环 10 次,每次调用 10 次 indi.mofan.ArthasDemo
类中的 getCar()
方法时的执行信息。
2.4 错误定位
死循环的定位
运行以下代码,这段代码会造成 死循环:
1 | private void deadLoop() { |
使用 dashboard
查看线程信息与内存信息:
main()
线程的 CPU 使用率达到 90%,内存中的 nonheap
和 metaspace
使用率非常高。
再使用 thread -n 3
查看当前 3 个最忙的线程:
死锁的定位
运行以下代码,这段代码会造成 死锁:
1 | private void deadLock() { |
利用 thread
全局查看线程信息:
可以看到当前有 27 个线程,其中有 2 个线程被阻塞。
还可以利用 thread -b
找出当前阻塞其他线程的线程:
2.5 时空隧道
tt
命令生成方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测。
记录每次调用方法的环境现场:
1 | tt -t 类全限定名 方法名 |
显示 tt
命令记录的时间片段:
1 | tt -l |
筛选出目标方法的时间片段:
1 | tt -s 'method.name=="目标方法名称"' |
查看某次方法的调用信息:
1 | tt -i 索引值 |
其中的索引值为执行 tt -t
、tt -l
等命令时第一列的值。
tt
命令保存了某次调用的所有现场信息,因此可以重做一次调用:
1 | tt -i 索引值 -p |
注意事项
tt
命令会将方法、函数的的入参、返回值等信息保存到一个 Map<Integer, TimeFragment>
中,默认大小为 100。
使用 tt
命令后,需要手动释放内存,否则在长时间使用下可能会导致 OOM,并且就算退出 arthas 也不会自动清除 map 缓存。
删除 map 缓存信息的方式:
1 | 通过对应索引删除指定的 tt 记录 |
2.6 生成火焰图
profiler
命令可以生成应用热点的火焰图。
启动 profiler
:
1 | profiler start |
获取已采集的用例数量:
1 | profiler getSamples |
查看 profiler
状态(查看当前 profiler
在采样哪种 event
和采样时间):
1 | profiler status |
默认生成的是 CPU 的火焰图,即 event
为 cpu
,可以使用 --event
指定采样 event
。
停止采样:
1 | profiler stop |
停止采样后,默认生成 HTML 格式的结果文件。
3. 奇技淫巧
参考链接:arthas的奇技淫巧-常用高级技巧(spring获取bean,ognl表达式,命令别名,调用方法,对象传参)
3.1 获取 Spring Bean
场景
- 执行 Spring 中某个 Bean 的特定方法
- 对某段代码做测试,但是当前这个方法不能直接通过接口访问
- 不想使用 telent invoke 调用 Dubbo Service 中的代码
- 线上观察 Spring 中的 Bean 信息
方法
首先执行以下命令:
1 | tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod -n 1 |
上述命令监听的是 MVC 的请求映射处理器适配器,因此所有接口的调用都会触发。-n
用于指定记录次数,不指定可能导致 OOM。
执行上述命令后访问任意接口,成功后会退出 tt
命令的监听(因为 -n 1
表示只监听一次),并输出一个 INDEX,第一次为 1000。返回执行上述命令,INDEX 会递增。
之后执行下述命令获取 Spring 容器中的 Bean 并调用对应方法:
1 | tt -i 1000 -w 'target.getApplicationContext().getBean("Bean 名称").方法名()' |
-w
选项表示使用 OGNL 表达式监听对应的方法。
3.2 调用静态方法
调用静态方法需要使用到 ognl
命令,用于执行 OGNL 表达式。
调用静态方法:
1 | ognl '@java.util.Objects@hashCode(1)' |
调用实例方法:
1 | ognl '@java.lang.System@out.println("hello world")' |
使用 ognl
命令的规则:
- 定位类:
@类的全限定名
,比如@java.util.Objects
- 定位类的
Class
对象:@类的全限定名@class
,比如@java.util.Objects@class
- 定位类的静态方法或静态字段:
@类的全限定名@方法名或字段名
,比如@java.lang.Thread@currentThread()
- 定位实例方法或实例字段:
@类的全限定名.方法名或字段名
,比如@java.lang.Thread@currentThread().name
3.3 传递对象参数
使用 tt
命令监听方法或使用 ognl
调用静态方法时,String
类型或者数字类型的参数可以直接传入,但对象类型的参数不能直接传参,也不支持以 JSON 的形式传递。
可以 灵活变通下,使用 ognl
命令将 JSON 字符串反序列化为对象,然后传入对应的方法即可。
比如:
1 | tt -i 1000 -w 'target.getApplicationContext().getBean("Bean 名称").方法名(@com.alibaba.fastjson.JSON@parseObject("{\\"name\\":\\"mofan\\",\\"age\\":23}", @com.example.Person@class))' |
这里使用 FastJson 将以下 JSON 字符串转换为 Person
对象:
1 | { |
3.4 查看内存数据
使用 vmtool
命令利用 JVMTI
接口,实现查询内存对象,强制 GC 等功能。
获取对象:
1 | vmtool --action getInstances --className com.example.SpringBootApplication --limit 1 |
执行方法:
1 | vmtool --action getInstances --className com.example.SpringBootApplication --limit 1 --express 'instances[0].方法名(参数)' |
与 tt
命令的区别:
tt
命令可以直接获取 Spring 中的 Bean,当知道 Bean 的名称时,可以直接通过其名称获取到 Bean 对象;- 如果需要获取的对象没有被 Spring 管理,那么就可以使用
vmtool
命令。
与 tt
一样使用 -n
选项类似,使用 vmtool
命令时也需要注意使用 --limit
选项。
4. 学习资源
第一学习资源当然是官网 arthas,官网除了提供 详尽的文档 外,还提供了 在线教程,让用户在实践中学习。
除此之外,在 Github 的 arthas 托管仓库中,Issues 中有个名为 user-case
的 Labels,这下面记录了 Arthas 的使用实例与最佳实践。