Arthas快速入门

常见使用场景

  • 正式环境遇见已经处理过的bug,但似乎没有执行到又出现了问题,是我忘记把代码提交到到git或svn上?还是线上代码版本不对?还是修复一个bug并生成了三个新的bug?
    常见的处理方法:检查项目的版本号及git或svn号,但如果系统中没有记录版本号及git或svn号,那么只能把包从现场拿回来反编译看源码
  • 正式环境突遇bug,需要发布补丁修复,但正式环境有大量用户正在正常使用,不能重启,该怎么办?
    常见的处理方法:先评估一下bug的严重程度,如果比较严重或者阻断流程,那么就必须发布补丁,更新并重启服务;如果不严重,那么就放在下一个版本里再进行修复。
  • 排查正式环境问题时,正式环境日志等级高于我们代码中使用的日志等级,导致线上日志没有打印出我们想要的日志。例如现场配置日志的level为INFO,但我们代码中日志的level为DEBUG。
    常见的处理方法:修改现场环境的日志等级,或我们修改代码中使用的日志等级并重新发布,但都无法避免重启服务才能生效。
  • 正式环境服务器CPU占用持续起飞,如何判断是哪里的问题?
    常见的处理方法:使用top命令找到占用CPU过高的进程,得到进程ID;使用top -Hp命令查找该进程中占用CPU过高的线程ID;使用jstack通过进程ID与转换成16进制的线程ID查看栈信息。或者通过查看日志各个接口的耗时判断系统瓶颈——比较繁琐且当进程占用CPU忽高忽低则可能不准确。
  • 正式环境遇到数据处理有问题,正式环境无日志记录,内部环境无法复现,如何判断是哪里处理异常?
    常见的处理方法:靠经验猜测;通过在该功能各个方法节点增加日志记录数据的输入输出再重新发布以重新观察。
  • 正式环境用户反馈某个功能很慢,而开发环境由于达不到现场数据的量级而无法复现,或测试人员在进行性能测试时反馈某个功能的接口耗时过长,如何排查优化?
    常见的处理方法:构造与现场数据量级相仿的数据后再在内部进行测试;通过阅读代码靠经验猜测;发布补丁,对每个方法单独计算耗时并记录日志,通过记录耗时结果针对代码进行优化。

简介

image.png
Arthas是Alibaba在 2018 年 9 月开源的 Java 诊断工具。允许开发者在不修改代码或重启服务器的基础上,实现Java应用程序的运行时诊断,基本使用场景是定位复现一些生产环境比较难以定位问题。可以在线排查问题,以及动态追踪Java代码,实时监控JVM状态等等。
官方文档:https://arthas.aliyun.com/doc/quick-start.html
image.png

命令列表

image.png
image.png

入门实战

下载

下载后发现它就是一个十几兆的压缩包
image.png

解压、运行

image.png

  1. 放到服务器上解压后,可得到文件目录如左图所示,由于是jar工具,所以需要依赖JDK,最低JDK版本支持为JDK6
  2. 运行方式:
  • linux
    运行as.sh
  • windows
    运行as.bat
  • 通用
    执行命令:java -jar arthas-boot.jar
    推荐使用通用方式运行,否则linux下还需要对脚本赋予执行权限

运行

image.png

  1. 根据界面提示选择对应的Java进程,这里的[1]/[2]..为序列号 1950 为进程pid,fd-cloud-tzzf-mysql-znbl-biz.jar为进程名称
  2. 找到我们需要排查的进程对应前面的序列号,按数字键 1/2/3 .. 然后回车
    image.png
    如图所示我们就已经登录上了Arthas客户端,接下来我们就可以尽情地使用各种各样的命令

jad命令——反编译指定已加载类的源码

小tips:所有命令都可用:命令 -h 查看帮助

使用示例

以笔录模块的com.fardo.modules.znbl.bl.api.ApiBlRecordController类为例

  • 查看源码
    jad com.fardo.modules.znbl.bl.api.ApiBlRecordController
  • 默认情况下,反编译结果里会带有ClassLoader等信息,通过—source-only选项,可以只打印源代码。
    jad --source-only com.fardo.modules.znbl.bl.api.ApiBlRecordController
  • 如果只想查看该类下的某个方法
    jad --source-only com.fardo.modules.znbl.bl.api.ApiBlRecordController page

retransform命令——加载外部的.class文件,让JVM重新加载该类。

使用示例

以笔录模块的com.fardo.modules.znbl.bl.api.ApiBlRecordController类page方法为例

  1. 在idea中修改我们的代码并编译
  2. 把编译后的.class文件上传到我们的服务器中
  3. 执行命令让JVM重新加载类
    retransform /faduit/tzzf-mysql/app/arthas-tempclass/ApiBlRecordController.class
  4. 每一次执行restransform都会生成一个restransform entry,可使用retransform -l命令查看,就像版本管理的Map
  5. 若想回退代码,则可以使用retransform --deleteAll / retransform -d {id}清除所有或指定id的restransform entry

使用限制

  1. 不允许新增加field/method
  2. 正在运行的函数,没有退出不能生效

logger命令 ——查看logger信息,更新logger level。

使用示例

以笔录模块的com.fardo.modules.znbl.bl.api.ApiBlRecordController类page方法为例

  1. 使用logger -n查找ApiBlRecordController类的classLoadeHash值
    logger -n com.fardo.modules.znbl.bl.api.ApiBlRecordController
  2. 单独设置ApiBlRecordController的logger level
    logger -c {classLoadeHash} -n com.fardo.modules.znbl.bl.api.ApiBlRecordController -l debug
  3. 全局设置logger level
    3.1 查看所有logger信息找到生效的logger名称
    logger
    3.2 设置logger level
    logger -c {classLoadeHash} -n ROOT -l DEBUG

watch命令 ——函数执行数据观测

使用示例

以笔录模块的com.fardo.modules.znbl.bl.api.ApiBlRecordController类page方法为例

  1. 使用watch命令观察函数调用入口的参数,可以看到入参是一个Obecjt[]
    watch com.fardo.modules.znbl.bl.api.ApiBlRecordController page '{params}'
  2. 调整-x的值, -x表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1。
    watch com.fardo.modules.znbl.bl.api.ApiBlRecordController page '{params}' -x 2

trace命令——输出方法内部调用路径,与方法路径上的每个节点上耗时

使用示例

以笔录模块的com.fardo.modules.znbl.bl.api.ApiBlRecordController类page方法为例

  1. 使用trace输出函数路径上的每个节点上耗时
    trace com.fardo.modules.znbl.bl.api.ApiBlRecordController page

注意事项

trace 命令能方便的帮助你发现和定位性能问题缺陷,但其每次只能跟踪一级方法的调用链路。(官网上如此说)——实际上也可以跟踪多级的调用链路,但会导致性能性能下降、统计时间不准,还是建议一层一层输出。

thread命令——查看当前线程信息,查看线程的堆栈

使用示例

  1. thread -n [value]一键展示当前最忙的前N个线程并打印堆栈
    thread -n 3
  2. thread -i [value], 指定采样时间间隔单位ms
    thread -i 5000
    13 展示指定间隔内最忙的N个线程
    thread -n 3 -i 5000

核心原理

JavaAgent中的Instrumentation接口

JavaAgent顾名思义就是一个Java代理,我们知道任何一项java应用的启动都需要有一个入口函数即main函数,加载从入口函数开始一直扩散到整个应用。
从JDK6开始,Java提供了一个新特性:Instrumentation功能,虽然这个接口包含的内容不多,但功能却很强大,这个接口主要提供了2个核心方法:retransformClasses()与redefineClasses(),retransformClasses是在已存在的字节码文件上修改后再替换之,而redefineClasses是自己提供字节码文件替换掉已存在的class文件。当然,运行时直接替换类很不安全。比如新的class文件引用了一个不存在的类,或者把某个类的一个field给删除了等等,这些情况都会引发异常。
所以这个功能修改类的实现方式是有限制的,例如不能添加、删除或重命名字段或方法、更改方法的签名或更改继承——官方注释:这些限制可能在未来的版本中被取消。
image.png
简单点说,JDK的agent就是在Java应用启动前或者运行时,JDK可以加载外部的一个agent包的代码,来动态修改或增强现有的代码逻辑。而Instrumentation就是由JVM提供的,在Java应用运行时通过agent来达到让我们在不重启JVM的前提下,动态地修改代码的功能。
市面上的一些热部署工具(Jrebel、IntelliJ IDEA等)、Java诊断工具(Arthas、Btrace等)都是基于该接口来实现。