> 文章列表 > arthas的简单使用

arthas的简单使用

arthas的简单使用

目录

  • arthas是什么
  • 为什么要使用arthas
  • arthas能做什么
  • 安装arthas
  • 前提准备
  • arthas主要命令
    • trace命令
    • watch命令
    • monitor命令
    • jad命令
    • dashboard命令
    • Thread命令
    • sc命令
    • mc命令
    • redefine命令
  • 实战演练
    • 1.定位到需要修改的类
    • 2.将定位到的.class文件反编译成.java文件
    • 3.修改.java文件
    • 4.将修改后的.java文件重新编译成.class文件
    • 5.将新的.class文件重新加载到JVM中
    • 6.验证

arthas是什么

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。

为什么要使用arthas

通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。

开发人员可以尝试在测试环境或者预发环境中复现生产环境中的问题。但是,某些问题无法在不同的环境中轻松复现,甚至在重新启动后就消失了。

如果您正在考虑在代码中添加一些日志以帮助解决问题,您将必须经历以下阶段:测试、预发,然后生产。这种方法效率低下,更糟糕的是,该问题可能无法解决,因为一旦 JVM 重新启动,它可能无法复现,如上文所述。

Arthas 旨在解决这些问题。开发人员可以在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。

arthas能做什么

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  • 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到 JVM 的实时运行状态?
  • 怎么快速定位应用的热点,生成火焰图?
  • 怎样直接从 JVM 内查找某个类的实例?

注意Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab自动补全功能,进一步方便进行问题的定位和诊断。

本文主要讲解arthastrace,jad,sc,mc,monitor,watch几个命令的使用。更多详细请查看arthas官方文档

接下来开始讲解各个命令的使用方法。

安装arthas

wget https://arthas.aliyun.com/arthas-boot.jar

使用以上命令直接下载arthas-boot.jar文件。

注意: 如果当前服务器没有信息jar文件,那么此时启动arthas会提示没有找到可执行的程序,此时只需要先启动一个可以调试的jar文件就行了。

在这里插入图片描述

前提准备

在讲解各个命令之前,我们需要准备一个jar包并且启动。本案例也是写了一个简单的demo并附带几个接口用来测试。

controller:

在这里插入图片描述

service:

在这里插入图片描述

然后打包为jar,上传到服务器并启动:

在这里插入图片描述

然后到arthas根目录,启动jarthas-boot.jar

在这里插入图片描述

一切准备就绪,接下来开始测试。

arthas主要命令

trace命令

作用:

方法内部调用路径,并输出方法路径上的每个节点上耗时

trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。

参数说明

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
[E] 开启正则表达式匹配,默认为通配符匹配
[n:] 命令执行次数
#cost 方法执行耗时
[m ] 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch ]。

这里重点要说明的是条件表达式条件表达式的构成主要由ognl 表达式组成,所以你可以这样写"params[0]<0",只要是一个合法的ognl表达式,都能被正常支持。

案例一:
trace com.laz.test.controller.TestController getByName

执行以上命令后,输出内容如下:
在这里插入图片描述
用此命令,我们可以来检查复杂业务中,方法内各个接口的调用耗时情况,根据耗时来进行优化。

使用ctrl+C可以退出trace命令。

案例二:

trace com.laz.test.controller.TestController getByName -n 1

在这里插入图片描述
此命令与案例一的区别是:此命令可通过-n后面跟的数字来确定执行几次后退出,而案例一需要手动通过ctrl+C退出。

案例三:
trace com.laz.test.controller.TestController getByName '#cost > 1'

在这里插入图片描述

此命令可以据调用耗时过滤出1S的调用路径,这个时间可以根据需要自行设置。

trace命令就介绍到这,如果想了解条件表达式的写法,可以自行去官方文档查看!

watch命令

作用

函数执行数据观测

参数说明

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 函数名表达式匹配
express 观察表达式,默认值:{params, target, returnObj}
condition-express 条件表达式
[b] 在函数调用之前观察
[e] 在函数异常之后观察
[s] 在函数返回之后观察
[f] 在函数结束之后(正常返回和异常返回)观察
[E] 开启正则表达式匹配,默认为通配符匹配
[x:] 指定输出结果的属性遍历深度,默认为 1,最大值是 4
[m ] 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch ]。

这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。

特别说明

  • watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后
  • 4个观察事件点 -b-e-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
  • 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参
  • 当使用-b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在

通过watch命令可以查看函数的参数/返回值/异常信息。

案例一:

watch com.laz.test.controller.TestController getByName returnObj

此命令可以查看方法执行的返回值

在这里插入图片描述
注意: 观察表达式,默认值是params, target, returnObj,这里只是列举了returnObj

案例二:

watch com.laz.test.controller.TestController getByName "{params,returnObj}" -x 2

此命令可以观察demo.TestController 类中getByName方法出参和返回值,结果属性遍历深度为2。

在这里插入图片描述

案例三:

watch com.laz.test.controller.TestController getByName "{params,returnObj}" -x 2 -b

此命令可以查看执行前参数
在这里插入图片描述
注意: 对比前一个例子,返回值为空(事件点为函数执行前(-b),因此获取不到返回值)

案例四:

watch com.laz.test.controller.TestController getByName "{target}" -x 2 -b

此命令可以查看方法中的属性

在这里插入图片描述
当然,也可以通过查看target.userService查看某一属性的值,如:watch com.laz.test.controller.TestController getByName "{target.userService}" -x 2 -b,想要了解详情,请自行查看官方文档。

monitor命令

作用

方法执行监控。 用来监视一个时间段中指定方法的执行次数,成功次数,失败次数,耗时等这些信息

对匹配 class-patternmethod-patterncondition-express的类、方法的调用进行监控。

monitor 命令是一个非实时返回命令.

实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。

服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何Arthas 命令不会引起原有业务逻辑的改变。

监控的维度说明

监控项 说明
timestamp 时间戳
class Java 类
method 方法(构造方法、普通方法)
total 调用次数
success 成功次数
fail 失败次数
rt 平均 RT
fail-rate 失败率

参数说明

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
[E] 开启正则表达式匹配,默认为通配符匹配
[c:] 统计周期,默认值为 120 秒
[b] 在方法调用之前计算 condition-express
[m ] 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch ]。

案例:
monitor com.laz.test.controller.TestController getByName -c 5

此命令可以监控getByName方法执行的次数,每5s刷新一次

在这里插入图片描述

jad命令

作用

反编译指定已加载类的源码

jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;

  • 在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便
  • 当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解

参数说明

参数名称 参数说明
class-pattern 类名表达式匹配
[c:] 类所属 ClassLoader 的 hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[E] 开启正则表达式匹配,默认为通配符匹配

案例一:
jad --source-only com.laz.test.controller.TestController

此命令可以反编译TestController类文件或者java类里面的某个方法。

在这里插入图片描述

在这里插入图片描述

dashboard命令

作用

查看当前系统的实时数据面板
输入 q 或者 Ctrl+C 可以退出dashboard命令

在这里插入图片描述
数据说明:

  • ID:Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应
  • NAME:线程名
  • GROUP:线程组名
  • PRIORITY:线程优先级, 1~10之间的数字,越大表示优先级越高
  • STATE:线程的状态
  • CPU%:线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。
  • TIME:线程运行总时间,数据格式为分:秒
  • INTERRUPTED:线程当前的中断位状态
  • DAEMON:是否是daemon线程

Thread命令

作用

线程相关堆栈信息。

参数说明

参数名称 参数说明
数字 线程id
[n:] 指定最忙的前N个线程并打印堆栈
[b] 找出当前阻塞其他线程的线程
[i ] 指定cpu占比统计的采样间隔,单位为毫秒

Arthas支持管道,可以用 thread 1 | grep 'main(' 查找到main class。

thread				#	显示所有线程的信息
thread 1			#	显示1号线程的运行堆栈
thread -b			#	查看阻塞的线程信息
thread -n 3			#	查看最忙的3个线程,并打印堆栈
thread -i 1000 -n 3	#	指定采样时间间隔,每过1000毫秒采样,显示最占时间的3个线程
thread --state WAITING  # 查看处于等待状态的线程(WAITING、BLOCKED

在这里插入图片描述
死锁线程查看

thread		# 查看线程状态
thread -b	#	查看阻塞的线程信息

在这里插入图片描述

sc命令

作用

查看 JVM 已加载的类信息

参数说明

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
[d] 输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的 ClassLoader 等详细信息。
如果一个类被多个 ClassLoader 所加载,则会出现多次
[E] 开启正则表达式匹配,默认为通配符匹配
[f] 输出当前类的成员变量信息(需要配合参数-d 一起使用)
[x:] 指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出
[c:] 指定 class 的 ClassLoader 的 hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[n:] 具有详细信息的匹配类的最大数量(默认为 100)
[cs ] 指定 class 的 ClassLoader#toString() 返回值。长格式[classLoaderStr ]

提示

  • class-pattern 支持全限定名,如 com.taobao.test.AAA,也支持 com/taobao/test/AAA 这样的格式,这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.啦。
  • sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true开关

案例一:
sc com.laz.test.*

此命令可以输出当前路径下所有的类。

在这里插入图片描述
案例二:
sc -d com.laz.test.controller.TestController

此命令可以打印类的详细信息

在这里插入图片描述

mc命令

作用

内存编译
Memory Compiler/内存编译器,编译.java文件生成.class

案例:

#	在内存中编译Hello.java为Hello.class
mc /root/Hello.java
#	可以通过-d命令指定输出目录
mc -d /root/bbb /root/Hello.java

在这里插入图片描述

redefine命令

作用

加载外部的.class文件,redefineJVM
注意, redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field)。
reset命令对redefine的类无效。如果想重置,需要redefine原始的字节码。
redefine命令和jad/watch/trace/monitor/tt等命令会冲突。执行完redefine之后,如果再执行上面提到的命令,则会把redefine的字节码重置。

redefine的限制

  • 不允许新增加field/method
  • 正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效。

使用步骤:

#	1. 使用jad反编译demo.MathGame输出到/root/MathGame.java
jad --source-only demo.MathGame > /root/MathGame.java#	2.按上面的代码编辑完毕以后,使用mc内存中对新的代码编译
mc /root/MathGame.java -d /root#	3.使用redefine命令加载新的字节码
redefine /root/demo/MathGame.class

在这里插入图片描述

实战演练

目的: 有时候我们再生产遇到一个紧急的问题,但是不方便重新发布服务或者发布的版本没有生效,此时就可以使用Arthas来临时修改线上代码,做到不发布版本就可以实现代码动态修改。

操作步骤:
1.定位到需要修改的类
2.将定位到的.class文件反编译成.java文件
3.修改.java文件
4.将修改后的.java文件重新编译成.class文件
5.将新的.class文件重新加载到JVM中

这里使用com.laz.test.controller.TestController这个类为例

1.定位到需要修改的类

sc -d com.laz.test.controller.TestController
在这里插入图片描述
注意: 复制这个classLoaderHash,接下来要用

2.将定位到的.class文件反编译成.java文件

jad -c 6267c3bb --source-only com.laz.test.controller.TestController > /opt/java/TestController.java

  • -c: 类所属 ClassLoader 的 hashcode
  • –source-only: 默认情况下,反编译结果里会带有ClassLoader信息,通过--source-only选项,可以只打印源代码
  • /opt/java/TestController.java 指的是反编译后的源文件放在哪里,并制定文件名

在这里插入图片描述
可以看到,java文件已经反编译成功。

3.修改.java文件

修改前:

在这里插入图片描述

修改后:

在这里插入图片描述

4.将修改后的.java文件重新编译成.class文件

mc -c 6267c3bb /opt/java/TestController.java

在这里插入图片描述
注意:/opt/java/com/laz/test/controller/TestController.class是编译后的class文件的路径.

5.将新的.class文件重新加载到JVM中

redefine -c 6267c3bb /opt/java/com/laz/test/controller/TestController.class
在这里插入图片描述
到这一步,就成功将新的.class文件加载到JVM中了。

6.验证

访问我们刚才修改的接口。

在这里插入图片描述
可以看到,我们通过反编译修改的代码已生效。

本篇文章就介绍到这里,更多详情,请参考官方文档。