Java诊断工具-Arthas入门与实践

Java诊断工具-Arthas入门与实践


什么是Arthas?

Arthas是Alibaba开源的Java诊断工具,支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式。

Arthas能做什么?

如果你是一个成熟的Java研发,相信你一定遇到过如下问题:
1.我改的代码怎么没效果?难道打包有问题?
2.擦,这里没怎没有加日志啊,为了加日志上次线又很麻烦!
3.这段代码在我本地好好的,怎么线上就有问题呢?
4.服务好端端的怎么假死了?希望能看到服务的实时状态,知道它到底在做什么!
5.这个服务似乎加载了一个错误的类,但是我不知道他是从哪个jar里加载来的!
… …

以上问题,借助Arthas都可以迎刃而解。

我在哪里可以下载Arthas?

点击这里下载Arthas-3.5.3,解压后即可使用

诚然,很多jvm相关的问题我们可以通过jdk本身提供的各种命令进行解决,但使用起来不够直观,功能也不完善,所以我们才需要借助Arthas这样的三方诊断工具,下边我们由浅入深的介绍Arthas的各种操作,如果你之前没有接触过诊断工具,相信它会极大的提升你的debug速度,助你快速定位问题。开始前建议先喝一杯水,因为下边全是干货。

快速入门

在这一小节里,我们将运行一个math-game的简单程序,这个程序每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。我们使用Arthas来attach这个程序,来初步掌握Arthas的使用。

1. 下载并运行math-game
curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar

程序运行起来后,你会看到如下日志:
在这里插入图片描述
这就代表我们正常启动了

2. 使用Arthas粘附目标程序

这里务必使用与启动math-game相同的账户,因为Arthas只能粘附当前用户发起的java进程:

sh as.sh

在这里插入图片描述
arthas会列出当前用户启动的java进程列表,如果你在这里无法认出哪个是你的目标进程,可以Ctrl + C退出,使用ps -ef | grep <关键字>来找到你的进程pid,再来这里找对应进程。
当然,本次我们很容易就能看出3号进程是我们的目标,于是输入3,arthas就会尝试去attach(粘附)这个进程:
请添加图片描述
看到这个ARTHAS的大logo,就证明attach成功了。

3. jad反编译源码

到这里,我们似乎还不知道这个jar里究竟写了什么内容,好在上图中,arthas已经为我们提示了这个jar的main class:demo.MatchGame,我们可以使用jad命令直接对其进行反编译:

jad demo.MathGame

在这里插入图片描述
可以看出,我们成功的反编译了main class的源码,并且arthas为我们额外展示了它的classLoader和所属jar。如果我们不想要这些额外信息,可以使用–source-only参数,仅展示源码。

4. watch查看方法返回结果

通过阅读源码,我们可以知道我们看到的日志,是通过demo.MathGame#primeFactors这个方法打印出来的,我们可以直接使用watch命令对这个方法进行观测,查看他的入参与返回结果:

watch demo.MathGame primeFactors {params,returnObj}

这条命令由四部分组成,分别是 watch命令,目标类的全限定名,目标方法,以及我们要看哪些信息,params即全部参数,returnObj是返回结果,这是ognl表达式的写法, 关于ognl详细可以看这里:apache官方ognl介绍当然,眼下我们还不需要关注这么多,执行命令得到以下结果:
在这里插入图片描述
由于程序中每秒都会调用一次这个方法,所以arthas会不断打印,可以按Q退出这次watch。仔细观察打印的结果,我们会发现虽然arthas打印了参数和结果,但是只显示了Object,并没有显示它的内容,这显然是没有意义的,原因是我们打印的遍历层次不够,我们在刚才这条命令的尾部,追加 -x 3参数,意思是,对params和returnObj最多向下遍历3层:

watch demo.MathGame primeFactors {params,returnObj} -x 3

在这里插入图片描述
这样,我们就能清晰的看到本次的入参是1,返回结果是2,59,和73组成的ArrayList。

5. trace打印内部调用路径

有时候,我们在线上会遇到这样的问题:
某个方法执行时间特别长,但是它的内部调用很深,我们不知道是哪个部分耗费的时间久。
这时候,就该trace出场了:

trace demo.MathGame primeFactors

我们使用trace跟踪这个方法,可以看到每一次方法内部调用路径信息:
在这里插入图片描述
除了调用层级外,我们还能在每次调用前看到当前函数耗费的时间,这对我们进行性能优化是非常有用的。

6. stack打印被调用路径

与上一个方法类似的,我们在项目中,可能会碰到同一个函数被多处调用的情况,stack命令可以为我们展示出调用路径:

stack demo.MathGame primeFactors

在这里插入图片描述
arthas还用非常多的实用命令,包括与虚拟机相关的 jvm, dashboard, vmoption,classloader,与线程相关的thread,以及生成火焰图的profiler等等,此处不一一介绍,可以参考arthas官方文档:

arthas命令列表

进阶操作

在快速入门中,我们使用了watch,stack,trace等命令,帮助我们观测程序执行情况,但这些都是被动观测,也就是说,只有等到程序执行到这里时,我们才能看到结果。有些时候,这不是很方便,比如:
1.如果这是个每天凌晨运行的定时任务怎么办?我不可能凌晨守在这里watch!
2.我的程序里在某个静态Map中get了一个key,我们不可能去watch HashMap的get方法,因为程序里有太多get了!(当然这种情况arthas也可以通过ognl有其他处理手段,但这里提供一种更优解)
下面这些命令,可以解决这些场景

1.tt时空隧道

watch命令固然好用,但是他对我们统计历史情况来说,不是很方便,而且使用watch需要我们提前想好要查看的表达式内容,所以我们有了tt这个命令,他可以帮我们记录下每一次调用时,当时的环境信息,并且给每一次调用提供了一个唯一 id,方便我们日后跟踪和replay。
使用起来很简单:

tt -t demo.MathGame primeFactors

在这里插入图片描述
可以看出,tt命令为我们记录下了每次请求的时间,耗时,是否正常返回,是否有异常等等信息,如果我们要查看详细的信息,可以使用 -i参数加index来查询,-s参数加ognl表达式来进行搜索,使用-play参数重新运行此请求,使用-w通过ognl表达式进行操作等等,此处不再赘述,可以参考官方文档。

2.getstatic查看静态属性

getstatic命令可以让我们直接查看某个类的静态成员,使用起来非常简单:

getstatic demo.MathGame random

在这里插入图片描述

3.调用静态方法

除了查看静态成员外,我们还可以通过ognl表达式来调用静态方法:

ognl '@System@out.println("haha")'

@代表这是一个静态成员,上例中,我们调用了System.out.println这个静态方法:
在这里插入图片描述
当然,由于这个方法没有返回值,我们无法在这里看到任何结果,但是这个调用方法,一样可以应用在我们项目中的其他静态方法上。

讲到这里,你的脑子里肯定会提出问题:你这说了半天,全是静态成员和静态方法啊!我如果想调用一个非静态方法怎么办?现在都是Spring项目,我想调用一个Controller或者service的方法怎么办?我想看一个Autowired进来的变量怎么办?balabalaba…

别急,下边这部分就是为你准备的

高阶操作

1. 调用由spring管理的类的方法

我们知道,我们在Spring项目中,注入的所有bean,都是由springContext进行管理的,也就是说,只要拿到springContext这个对象,我们就能通过它get到我们想要的实例,并调用其中的方法,或查看其成员,为所欲为。
问题是如何拿到这个springContext。学习过springmvc的同学一定对RequestMappingHandlerAdapter这个类不陌生,他可以帮我们的请求找到对应的Handler来处理,而这个类中,就包含我们想要的context对象。

首先,使用tt对其进行跟踪,并从页面请求一次这个服务的任意接口:

tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod

在这里插入图片描述
可以看到我们成功的记录到了这次请求,接下来,我们通过-w参数,执行ognl来获取context:

tt -i 1000 -w 'target.getApplicationContext()'

在这里插入图片描述
如图,我们成功获取了context对象,接下来,我们可以直接调用getBean方法,获取我们的service,并执行他的queryExample方法:

tt -i 1000 -w 'target.getApplicationContext().getBean("msgServiceImpl").queryExample("test")'

遗憾的是,执行失败了。执行结果提示出现了空指针异常,跟踪arthas的日志发现,原因是我们这个queryExample内部依赖了一个mybatis的mapper对象进行sql查询,而这个对象现在是空的,并没有被注入进来,调用时必然空指针了,所以我们的表达式需要写的再复杂一些:

tt -i 1000 -w '#mapper=target.getApplicationContext().getBean("msgNotifyMapper"),#service=target.getApplicationContext().getBean("msgServiceImpl"),#service.msgNotifyMapper=#mapper,#service.queryExample("test")'

可能看到这里你会有些晕,不要慌,我们拆开看:

tt -i 1000 -w 
'
//获得Mapper对象的bean
#mapper=target.getApplicationContext().getBean("msgNotifyMapper"), 
//获得service对象的bean
#service=target.getApplicationContext().getBean("msgServiceImpl"),
//将service中的mapper对象赋值为我们刚获得的mapper bean
#service.msgNotifyMapper=#mapper,
//调用query方法
#service.queryExample("test")
'

这样,我们就能成功的调用queryExample方法!可以看出,使用ognl,我们可以完成更多复杂的逻辑,也就能根据我们的需求,做出更细致的操作~但有时候,我们靠这样的方法还是不能够准确定位问题,我们需要把线上的代码按我们的需求做一些调整,比如修改某个判断条件,添加一行日志,调整循环的次数等等,这时候,我们可以借助更多命令,来实时热部署线上代码。

2. 实时修改线上代码

在开头,我们学习过jad的使用,但那时,我们只是把源码反编译并展示在屏幕上,
实际上,我们可以将它保存成java文件,就和linux命令一样:

jad --source-only demo.MathGame > /Users/sunyuhan/tmpFile/MathGame.java

在这里插入图片描述
我们将源码保存下来后,就可以使用vim或者下载下来进行编辑了,编辑后,使用mc命令内存编译回class:

mc /Users/sunyuhan/tmpFile/MathGame.java -d /Users/sunyuhan/tmpFile/MathGame.class

在这里插入图片描述
最后,重点来了,使用retransform命令,将这个class替换掉jvm中正在运行的class:

retransform /Users/sunyuhan/tmpFile/MathGame.class/demo/MathGame.class

在这里插入图片描述
无需中断项目,就可以完成代码的热部署!

最后,还是有一些需要注意的点,虽然retransform非常实用,但是我仍然不建议你在生产环境这样做,没有走标准的测试流程,生产环境的代码不应当被随意的改变,这是一个非常危险的操作!并且由于我们是在jvm层面做的调整,在你重启整个项目后,重新从部署的包中加载内容回jvm,仍然是旧的内容,除非我们将这些jar中的class也进行替换。


参考文献:

  • [1].arthas官方文档 https://arthas.aliyun.com/doc/index.html
  • [2].ognl官方文档 https://commons.apache.org/proper/commons-ognl/language-guide.html
  • [3].arthas项目github https://github.com/alibaba/arthas/issues/71
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值