Arthas教程
文章目录
Arthas 介绍
什么是Arthas,它能为我们做什么
Arthas
是Alibaba开源的Java诊断工具,深受开发者喜爱。
当你遇到以下类似问题而束手无策时,Arthas
可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
Arthas
支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 Tab
自动补全功能,进一步方便进行问题的定位和诊断。
Arthas相关地址
基础教程
启动arthas-boot
下载arthas-boot.jar
,再用java -jar
命令启动
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar --target-ip 0.0.0.0
arthas-boot
是Arthas
的启动程序,它启动后,会列出所有的Java进程。
选择需要诊断的目标进程,再Enter/回车
,Attach成功之后,会打印Arthas LOGO。
输入 help
获取到更多命令的帮助信息。输入对应命令+ --help 查看该命令帮助信息。如dashboard --help
常用命令
Dashboard
dashboard
命令可以查看当前系统的实时数据面板。
输入 Q
或者 Ctrl+C
可以退出dashboard命令。
Thread
thread
命令会打印线程的栈。
Arthas支持管道,可以用 thread 1 | grep main
查找到main class
。
可以看到main class
是demo.MathGame
:
Sc
可以通过 sc
命令来查找JVM里已加载的类:
sc -d *MathGame
Jad
可以通过 jad
命令来反编译代码:
jad demo.MathGame
通过--source-only
参数可以只打印出在反编译的源代码
jad --source-only com.example.demo.arthas.user.UserController
Watch
通过watch
命令可以查看函数的参数/返回值/异常信息。
watch demo.MathGame primeFactors returnObj
输入 Q
或者 Ctrl+C
退出watch命令。
Exit/Shutdown
-
退出Arthas
用
exit
或者quit
命令可以退出Arthas。退出Arthas之后,还可以再次用
java -jar arthas-boot.jar
来连接。 -
彻底退出Arthas
exit/quit
命令只是退出当前session,arthas server还在目标进程中运行。想完全退出Arthas,可以执行
shutdown
命令。
进阶教程
常用命令
查看JVM信息
sysprop
-
可以通过
sysprop
打印所有的System Properties信息sysprop
-
也可以指定单个key:
sysprop java.version
-
也可以通过
grep
来过滤:sysprop | grep user
-
可以设置新的value:
sysprop key value
例如:sysprop testKey testValue
sysenv
可以sysenv
命令获取到环境变量。和sysprop
命令类似。
sysenv
jvm
jvm
命令会打印出JVM
的各种详细信息。
jvm
查看已加载的类
sc
sc
命令可以查找到所有JVM已经加载到的类。
如果搜索的是接口,还会搜索所有的实现类。比如查看所有的Filter
实现类:
sc javax.servlet.Filter
通过-d
参数,可以打印出类加载的具体信息,很方便查找类加载问题。
sc -d javax.servlet.Filter
sc
支持通配,比如搜索所有的StringUtils
sc *StringUtils
sm
sm
命令则是查找类的具体函数。
sm java.math.RoundingMode
通过-d
参数可以打印函数的具体属性
sm -d java.math.RoundingMode
也可以查找特定的函数,比如查找构造函数
sm java.math.RoundingMode <init>
使用技巧
自动补全
Arthas支持丰富的自动补全功能,在使用有疑惑时,可以输入Tab
来获取更多信息。
比如输入 sysprop java.
之后,再输入Tab
,会补全出对应的key:
readline的快捷键支持
Arthas支持常见的命令行快捷键,比如Ctrl + A
跳转行首,Ctrl + E
跳转行尾。
更多的快捷键可以用 keymap
命令查看。
历史命令的补全
如果想再执行之前的命令,可以在输入一半时,按Up/↑
或者 Ddown/↓
,来匹配到之前的命令。
比如之前执行过sysprop java.version
,那么在输入sysprop ja
之后,可以输入Up/↑
,就会自动补全为sysprop java.version
。
如果想查看所有的历史命令,也可以通过 history
命令查看到。
pipeline
Arthas支持在pipeline之后,执行一些简单的命令,比如:
sysprop | grep java
sysprop | wc -l
Ognl
在Arthas里,有一个单独的ognl
命令,可以动态执行代码。
-
调用static函数
ognl '@aaa@qq.com("hello ognl")'
-
获取静态类的静态字段
获取
UserController
类里的logger
字段ognl -c 1be6f5c3 @aaa@qq.com
还可以通过
-x
参数控制返回值的展开层数。ognl -c 1be6f5c3 -x 2 @aaa@qq.com
-
执行多行表达式,赋值给临时变量,返回一个List
ognl '#aaa@qq.com@getProperty("java.home"), #aaa@qq.com@getProperty("java.runtime.name"), {#value1, #value2}'
更多
在Arthas里ognl
表达式是很重要的功能,在很多命令里都可以使用ognl
表达式。
一些更复杂的用法,可以参考:
- OGNL特殊用法请参考:https://github.com/alibaba/arthas/issues/71
- OGNL表达式官方指南:https://commons.apache.org/proper/commons-ognl/language-guide.html
使用案例
排查函数调用异常
现象
目前,访问 http://localhost/user/0 ,会返回500异常
curl http://localhost/user/0
但请求的具体参数,异常栈是什么呢?
查看UserController的 参数/异常
在Arthas里执行
watch com.example.demo.arthas.user.UserController * '{params, throwExp}'
- 第一个参数是类名,支持通配
- 第二个参数是函数名,支持通配
访问 curl http://localhost/user/0
,watch
命令会打印调用的参数和异常
可以看到实际抛出的异常是IllegalArgumentException
。
可以输入 Q
或者 Ctrl+C
退出watch命令。
如果想把获取到的结果展开,可以用-x
参数
返回值表达式
在上面的例子里,第三个参数是返回值表达式
,它实际上是一个ognl
表达式,它支持一些内置对象:
- loader
- clazz
- method
- target
- params
- returnObj
- throwExp
- isBefore
- isThrow
- isReturn
你可以利用这些内置对象来组成不同的表达式。比如返回一个数组
watch com.example.demo.arthas.user.UserController * '{params[0], target, returnObj}'
更多参考: https://alibaba.github.io/arthas/advice-class.html
条件表达式
watch
命令支持在第4个参数里写条件表达式,比如
watch com.example.demo.arthas.user.UserController * returnObj 'params[0] > 100'
当访问 http://localhost/user/1时,watch
命令没有输出
当访问 http://localhost/user/111时,watch
会打印出结果
当异常时捕获
watch
命令支持-e
选项,表示只捕获抛出异常时的请求
按照耗时进行过滤
watch
命令支持按请求耗时进行过滤
watch com.example.demo.arthas.user.UserController * '{params, returnObj}' '#cost>200'
热更新代码
通过jad
/mc
/redefine
命令实现动态更新代码的功能
现象
目前,访问 http://localhost/user/0 ,会返回500异常
下面通过热更新代码,修改这个逻辑。
jad反编译UserController
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
jad反编译的结果保存在 /tmp/UserController.java
文件里了,然后用vim来编辑/tmp/UserController.java
vim /tmp/UserController.java
我们将代码修改为如下
sc查找加载UserController的ClassLoader
sc -d *UserController | grep classLoaderHash
可以发现是 spring boot aaa@qq.com
加载的。
mc编译UserController
保存好/tmp/UserController.java
之后,使用mc
(Memory Compiler)命令来编译,并且通过-c
参数指定ClassLoader
mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
redefine重新加载新编译好class文件
使用redefine
命令重新加载新编译好的UserController.class
redefine /tmp/com/example/demo/arthas/user/UserController.class
查看修改后的结果
redefine
成功之后,再次访问 http://localhost/user/0 ,结果如下:
动态更新应用日志级别
查找UserController的ClassLoader
sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash
用ognl获取logger
ognl -c 1be6f5c3 '@aaa@qq.com'
可以知道aaa@qq.com
实际使用的是logback。可以看到level=null
,则说明实际最终的level是从root
logger里来的。
单独设置UserController的logger level
ognl -c 1be6f5c3 '@aaa@qq.comsetLevel(@aaa@qq.com)'
再次获取aaa@qq.com
,可以发现已经是DEBUG
了
修改logback的全局logger level
通过获取root
logger,可以修改全局的logger level
ognl -c 1be6f5c3 '@aaa@qq.com("root").setLevel(@aaa@qq.com)'
排查logger冲突问题
确认应用使用的logger系统
以UserController
为例,它使用的是slf4j api,但实际使用到的logger系统是logback
ognl -c 1be6f5c3 '@aaa@qq.com'
获取logback实际加载的配置文件
ognl -c 1be6f5c3 '#aaa@qq.com@getLogger("root").loggerContext.objectMap, #map1.get("CONFIGURATION_WATCH_LIST")'
查找可能存在的logger配置文件
classloader -c 1be6f5c3 -r logback-spring.xml
可以知道加载的配置的具体来源,尝试加载容易冲突的文件
获取Spring Context
案例步骤:获取spring context -> 获取bean -> 调用函数
获取spring context
使用tt命令获取到spring context
tt
即 TimeTunnel,它可以记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测。参考文档:https://alibaba.github.io/arthas/tt.html
tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod
访问
可以看到tt
命令捕获到了如下请求
从调用记录里获取到spring context
使用tt命令从调用记录里获取到spring context
输入 Q
或者 Ctrl + C
退出上面的 tt -t
命令。
输入如下命令查看index为1000的请求的信息
tt -i 1000 -w 'target.getApplicationContext()'
获取spring bean,并调用函数
tt -i 1000 -w 'target.getApplicationContext().getBean("helloWorldService").getHelloMessage()'
上一篇: spfa+01分数规划【观光奶牛】
下一篇: 淘宝 TFS 文件系统/分布式文件系统