前言
Arthas是Alibaba开源的Java诊断工具,深受开发者喜爱。 官方对它的定位是帮助我们解决线上的问题。由于环境不同,经常会出现用户实际使用时出现的问题我们测试环境无法复现,我们本机调试运行正常,但是到了测试环境就出现问题了。这时候arthas就可以帮助我们快速的定位方法的调用过程,快速复现问题并及时的解决问题。同时,arthas的火焰图和trace命令还可以帮我们快速检测出方法中耗时最长的部分,利于我们优化代码效率,热编译可以帮助我们调试的时候更快的更新自己的代码,减少编译等待的时间。
文档
GitHub
官方文档
基础教程
安装
-
下载路径
-
下载
# 推荐方法 -> 下载jar包到本地直接执行 curl -O https://arthas.aliyun.com/arthas-boot.jar # linux/unix/mac 可以一键安装, 然后通过 ./as.sh 启动 curl -L https://arthas.aliyun.com/install.sh | sh -
打印帮助信息
java -jar arthas-boot.jar -h -
启动
java -jar arthas-boot.jar # 端口被占用时可以添加参数修改 tenlet 和http的端口 java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
常用命令
-
dashboard 查看系统实时数据面板
# 每1000ms显示一次, 显示5次 dashboard -i 1000 -n 5 -
thread 查看当前线程信息, 查看线程堆栈
# 列出5000ms内最忙的3个线程 thread -i 5000 -n 3 # 查看第一页线程信息 thread # 查看所有线程信息 thread -all # 查看阻塞线程(目前只支持synchronized阻塞的) thread -b # 查看指定状态线程 thread --state WAITING # 查看指定线程 thread 3 -
jad 反编译目标方法或类的代码
# 反编译指定类 jad --source-only com.example.demo.controller.TestController # 反编译指定类中的方法 jad --source-only com.example.demo.controller.TestController a -
mc
Memory Compiler内存编译器, 编译.java生成class
# 编译java文件 -d 指定编译文件路径 mc E:/Downloads/Ding/demo/src/main/java/com/example/demo/controller/TestController.java -d E:/Downloads/Ding/demo/src/main/java/ -
retransform
加载外部的.class文件. 覆盖JVM已加载的类
限制:
- 不允许新增加field/method
- 正在运行的函数,没有退出不能生效
# 用编译好的class文件替换掉jvm中已加载的类 retransform E:/Downloads/Ding/demo/src/main/java/com/example/demo/controller/TestController.class # 查看retransform entry retransform -l # 删除指定retransform entry retransform -d 1 # 删除所有retransform entry retransform --deleteAll # 显示触发 retransform ( entry中有最新的transform时使用最新的, 没有时还原) retransform --classPattern com.example.demo.controller.TestController消除retransform影响:
-
删除这个类对应的 retransform entry ( 一般直接删除对应的entry之后类即会还原, 如果对一个类执行多次retransform, 则可能需要执行重新触发retransform命令来消除)
-
重新触发retransform
如果不清除掉所有的 retransform entry,并重新触发 retransform ,则arthas stop时,retransform过的类仍然生效。
-
ognl
# 调用静态函数 ognl '@com.example.demo.controller.TestController@staticVoid()' # 调用静态类的静态字段 ognl '@com.example.demo.controller.TestController@LOOP_COUNT' # 执行多行表达式,赋值给临时变量,返回一个List: ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #vaue2}' -
monitor
监控项 说明 timestamp 时间戳 class Java类 method 方法(构造方法、普通方法) total 调用次数 success 成功次数 fail 失败次数 rt 平均RT fail-rate 失败率 # 监视指定方法的执行记录 monitor -c 5 com.example.demo.controller.TestController a # 监视的时候按照ognl表达式添加过滤 例如如下表示第一个参数为"1"时才进入 monitor monitor -c 5 com.example.demo.controller.TestController a 'params[0] == "1"' -
✨watch 查看指定函数的返回值
参数名称 参数说明 class-pattern 类名表达式匹配 method-pattern 方法名表达式匹配 express 观察表达式 condition-express 条件表达式 [b] 在方法调用之前观察 [e] 在方法异常之后观察 [s] 在方法返回之后观察 [f] 在方法结束之后(正常返回和异常返回)观察 [E] 开启正则表达式匹配,默认为通配符匹配 [x:] 指定输出结果的属性遍历深度,默认为 1 # 查看方法入参 watch com.example.demo.controller.TestController a '{params}' -x 3 -b # 查看{方法出参, 返回值, 抛出的异常} # -v 表示会打印Condition express的具体值和执行结果,方便确认 # -n 5表示只执行5次 # -x 3 表示参数的深度为 3, 不设置时默认深度为 1 # params.length > 0 限定传参不为空时进入 watch watch com.example.demo.controller.TestController a '{params,returnObj,throwExp}' -v -n 5 -x 3 'params.length > 0' # 限定第二个参数的ID为 1 时进入 watch watch com.example.demo.controller.TestController a '{params,returnObj,throwExp}' -v -n 5 -x 3 'params[1].id == 1' # 查看方法调用前和调用后的参数 # -b 在方法调用前观察 # -s 在方法调用后观察 watch com.example.demo.controller.TestController a '{params,returnObj,throwExp}' -v -n 5 -x 3 -b -s 'params[1].id == 1' # 按照耗时过滤, 耗时大于6000ms时进入 watch watch com.example.demo.controller.TestController a '{params,returnObj,throwExp}' -v -n 5 -x 3 '#cost > 6000' # 查看当前对象属性 watch com.example.demo.controller.TestController a 'target' # 查看当前对象的常量值 watch com.example.demo.controller.TestController a 'target.LOOP_COUNT' -
✨profiler
使用async-profiler生成火焰图, 找出占用时间较长的方法
# 启动统计 profiler start # 查看采样数(非必须) profiler getSamples # 结束统计 profiler stop -
✨trace
显示各个方法的执行耗时
# trace 单个方法 trace com.example.demo.controller.TestController a -v -n 5 --skipJDKMethod false '1==1' # trace 多个方法 trace -E com.example.demo.controller.TestController a|b|c -n 5 -v --skipJDKMethod false '1==1' -
✨tt TimeTunnel [时间隧道]
# 记录 tt -t com.example.demo.controller.TestController a -n 1 # 重新触发一次 tt -p -i 1000 # 获取之前执行的参数 返回值等 tt -w '{method.name,params,returnObj,throwExp}' -x 3 -i 1000 # 周期性触发三次 tt -p --replay-times 3 --replay-interval 2000 -i 1000 # 记录springMVC 中的RequestMappingHandlerAdapter#invokeHandlerMethod的请求(因为这个对象可以通过getApplicationContext())获取到对应的值 tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod # 按照监视的对象获取Spring容器的ApplicationContext tt -i 1000 -w 'target.getApplicationContext()' # 从容器中取出任意的Bean! tt -i 1000 -w 'target.getApplicationContext().getBean("testController")' # 执行Bean中的方法 tt -i 1000 -w 'target.getApplicationContext().getBean("testController").updateUserInfo(null)' # 执行Bean中的方法(传递参数为对象时的写法) tt -i 1000 -w 'target.getApplicationContext().getBean("testController").updateUserInfo((#user = new com.example.demo.pojo.User(), #user.setId(1), #user.setName("testName") , #user.setBook((#book = new com.example.demo.pojo.Book(), #book.setBookId(2), #book)), #user))' # 添加获取ApplicationContext的类后 ognl -x 3 '#springContext=@com.example.demo.util.ApplicationContextProvider@context,#springContext.getBean("testController").printMail()' # 查看配置项信息 ognl -x 3 '#springContext=@com.example.demo.util.ApplicationContextProvider@context,#springContext.getEnvironment().getProperty("test.value")'