arthas
一、简介
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
Arthas(阿尔萨斯)能为你做什么?
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从 JVM 内查找某个类的实例?
二、下载、启动、退出
直接访问下面连接下载jar包
https://arthas.aliyun.com/arthas-boot.jar
离线模式,要下载全量包
链接: 全量包
1、启动:
需先启动我们要监控的项目。
1、然后再启动arthas.jar
java -jar arthas-boot.jar
// 更换端口
java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
2、然后根据提示选择我们要诊断的服务。输入数字,回车选择
启动完成展示
可以输入上面ip地址打开网页版
可以填入 IP,远程连接其它机器上的 arthas。
2、退出
如果只是退出当前的连接,可以用quit
或者exit
命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出 arthas,可以执行stop
命令。
如果报错:java.util.ServiceConfigurationError: com.sun.tools.attach.spi.AttachProvider: Provider sun.tools.attach.WindowsAttachProvider could not be instantiated
就将 jdk/jre/bin/attach.dll,复制到 jre/bin目录下重新运行即可
如我的目录
三、命令理解与示例
以下示例中 com.example.demo.api.TestController是类路径,test是方法,test1是方法里的方法,自己要注意看
3.1、基本命令
base64
- base64 编码转换,和 linux 里的 base64 命令类似cat
- 打印文件内容,和 linux 里的 cat 命令类似cls
- 清空当前屏幕区域echo
- 打印参数,和 linux 里的 echo 命令类似grep
- 匹配查找,和 linux 里的 grep 命令类似help
- 查看命令帮助信息history
- 打印命令历史pwd
- 返回当前的工作目录,和 linux 命令类似- `quit - 退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
reset
- 重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类session
- 查看当前会话的信息stop
- 关闭 Arthas 服务端,所有 Arthas 客户端全部退出tee
- 复制标准输入到标准输出和指定的文件,和 linux 里的 tee 命令类似version
- 输出当前目标 Java 进程所加载的 Arthas 版本号keymap
- Arthas 快捷键列表及自定义快捷键
3.2、JVM常用命令
1、dashboard - 当前系统的实时数据面板
2、thread - 查看当前 JVM 的线程堆栈信息
参数说明
参数名称 | 参数说明 |
---|---|
id | 线程 id |
[n:] | 指定最忙的前 N 个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i ] | 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200 |
[–all] | 显示所有匹配的线程 |
示例
写个死循环
1、没有参数时,显示第一页线程的信息
默认按照 CPU 增量时间降序排列,只显示第一页数据。
$ thread # 展示第一页线程
2、查看详情,可以找到死循环代码的地方
thread id # 显示指定线程的运行堆栈
3、thread -n 3 查看最忙的前 N 个线程
$ thread -n 3 # 展示前3个最忙的线程,3随便换
4、其他常用命令
$ thread --all, 显示所有匹配的线程
$ thread -b, 找出当前阻塞其他线程的线程
$ thread -i 1000, 统计最近 1000ms 内的线程 CPU 时间
3、ognl - 执行 ognl 表达式
1、调用静态函数
$ ognl '@[email protected]("hello")'
程序里就会输出hello
2、获取静态类的静态字段
$ ognl '@包名/类名@静态方法/字段'
还可以操作静态字段,如输出大小、添加值等等。
3.3、class/classloader 常用命令
1、sc - 查看 JVM 已加载的类信息(search Class找类)
1、查看这个包有哪些类,支持模糊搜索
$ sc com.example.demo.*
$ sc -d com.example.demo.* # -d打印类的详细信息
$ sc -d -f com.example.demo.* # -f打印出类的 Field 信息
2、sm - 查看已加载类的方法信息(search Method找方法)
1、查找这个类有哪些方法,支持模糊
$ sm com.example.demo.api.*
$ sm -d com.example.demo.api.* # -d打印方法的详细信息
3、jad - 反编译指定已加载类的源码
感觉线上的代码和当前不一致时可以使用
$ jad 类名
只需要某个方法时,指定方法名
$ jad 类名 方法名
将反编译的文件放到某个目录,目录是相对于项目的位置
# --source-only反编译时只显示源代码
$ jad --source-only com.example.demo.api.TestController > ./newClass/TestController.java
4、mc - 内存编译器,内存编译.java文件为.class文件
1、将某个java文件编译到某个.class文件。
先修改刚刚编译下来的java文件,修改后反编译回去
$ vim ./newClass/TestController.java # 修改代码
编译java文件
#-d命令指定输出目录
$ mc ./newClass/TestController.java -d ./newClass
5、redefine - 加载外部的.class文件,redefine 到 JVM 里
使用redefine命令加载新的字节码
$ redefine ./newClass/com/example/demo/api/TestController.class
通过测试上述流程发现可实现动态改线上代码 通过jad反编译出来代码,修改完后再编译为class文件,最后通过redefine加载class文件到jvm里
3.4、monitor/watch/trace 相关命令
1、monitor - 方法执行监控
可用于监控方法执行时间,用monitor的监控avg-rt(平均执行时间)看
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[c:] | 统计周期,默认值为 120 秒 |
[b] | 在方法调用之前计算 condition-express |
# -c 5秒刷新一次
$ monitor 类名 方法名 -c 5
监控的维度说明:
监控项 | 说明 |
---|---|
timestamp | 时间戳 |
class | Java 类 |
method | 方法(构造方法、普通方法) |
total | 调用次数 |
success | 成功次数 |
fail | 失败次数 |
rt | 平均执行时间 |
fail-rate | 失败率 |
2、watch - 方法执行数据观测
让你能方便的观察到指定函数的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。
参数说明:
watch 的参数比较多,主要是因为它能在 4 个不同的场景观察对象
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 函数名表达式匹配 |
express | 观察表达式,默认值:{params, target, returnObj} |
condition-express | 条件表达式 |
[b] | 在函数调用之前观察 |
[e] | 在函数异常之后观察 |
[s] | 在函数返回之后观察 |
[f] | 在函数结束之后(正常返回和异常返回)观察 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[x:] | 指定输出结果的属性遍历深度,默认为 1,最大值是 4 |
特别说明:
- watch 命令定义了 4 个观察事件点,即 -b 函数调用前,-e 函数异常后,-s 函数返回后,-f 函数结束后
- 4 个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
- 这里要注意函数入参和函数出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表函数入参外,其余事件都代表函数出参
- 当使用 -b 时,由于观察事件点是在函数调用前,此时返回值或异常均不存在
- 在 watch 命令的结果里,会打印出location信息。location有三种可能值:AtEnter,AtExit,AtExceptionExit。对应函数入口,函数正常 return,函数抛出异常。
示例:
1、查看方法返回值
$ watch com.example.demo.api.TestController test
2、查看方法入参
# 观察表达式,默认值是{params, target, returnObj}
# -x 属性遍历深度
# -b 在函数调用之前观察
$ watch com.example.demo.api.TestController test "{params,returnObj}" -x 2 -b
我的传参是123
3、同时观察函数调用前和函数返回后
#-x 属性遍历深度
#-b 在函数调用之前观察
#-s 在函数返回之后观察
#-n 只执行两次
$ watch com.example.demo.api.TestController test -x 2 -b -s -n 2
4、条件表达式的例子
只观察参数小于0的
# 观察表达式,默认值是{params, target, returnObj}
# 条件第一个参数<0
#-x 属性遍历深度
watch com.example.demo.api.TestController test "{params,returnObj}" "params[0]<0" -x 2
3、trace - 方法内部调用路径,并输出方法路径上的每个节点上耗时
trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式,使用ognl表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] | 命令执行次数 |
#cost | 方法执行耗时,单位是毫秒 |
示例:
1、监控方法的链路与耗时
$ trace 类名 方法名
如: trace com.example.demo.api.TestController test
$ trace 类名 方法名 '#cost>1000' #只监控耗时超过1m的
如:trace com.example.demo.api.TestController test '#cost>1000'
4、stack - 输出当前方法被调用的调用路径
很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式,使用ognl表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] | 命令执行次数 |
1、监控test1方法在哪里被调用
$ stack com.example.demo.api.TestController test1
# 据条件表达式来过滤
$ stack com.example.demo.api.TestController test1 'params[0]<0' -n 2
# 据执行时间来过滤
$ stack com.example.demo.api.TestController test1 '#cost>1000'
5、tt - 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。
这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。
于是乎,TimeTunnel 命令就诞生了。
1、记录调用
$ tt -t com.example.demo.api.TestController test
如果发现这里是true,就说明这个时候发生了异常
表格字段说明
表格字段 | 字段解释 |
---|---|
INDEX | 时间片段记录编号,每一个编号代表着一次调用,后续 tt 还有很多命令都是基于此编号指定记录操作,非常重要。 |
TIMESTAMP | 方法执行的本机时间,记录了这个时间片段所发生的本机时间 |
COST(ms) | 方法执行的耗时 |
IS-RET | 方法是否以正常返回的形式结束 |
IS-EXP | 方法是否以抛异常的形式结束 |
OBJECT | 执行对象的hashCode(),注意,曾经有人误认为是对象在 JVM 中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体 |
CLASS | 执行的类名 |
METHOD | 执行的方法名 |
2、显示所有已经记录的列表
$ tt -l
3、查看调用信息
如果观察记录列表发生异常了,可以用这个查看入参与返回值,从而排查问题
$ tt -i 1001
3.5、profiler/火焰图
profiler 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。
1、命令
$ profiler start # 启动
$ profiler getSamples # 获取已采集的 sample 的数量
$ profiler status #查看 profiler 状态
$ profiler stop --format html #停止 profiler,生成 html 格式,可以用--format参数指定生成格式
访问 http://localhost:3658/arthas-output/
查看到arthas-output目录下面的 profiler 结果:
四、idea的arthas插件
将光标放置在具体的类、字段、方法上面 右键选择需要执行的命令,部分会有窗口弹出、根据界面操作获取命令;部分直接获取命令复制到了剪切板
,自己启动 arthas 后粘贴命令即可执行。