写点什么

Arthas- 技术专题 - 使用指南

发布于: 2021 年 05 月 12 日
Arthas-技术专题-使用指南

Arthas 是 Alibaba 开源的 Java 诊断工具,采用命令行交互模式,提供了丰富的功能,是排查 jvm 相关问题的利器。

在逛 github 时,发现这款利器,深入了解之后,简直惊为天人。下面先列举一下它能做的一些事情:

  • 提供性能看板,包括线程、cpu、内存等信息,并且会定时的刷新。

  • 根据各种条件查看线程快照。比如找出 cpu 占用率最高的 n 个线程等

  • 输出 jvm 的各种信息,如 gc 算法、jdk 版本、ClassPath 等

  • 查看/设置 sysprop 和 sysenv

  • 查看某个类的静态属性,也可以通过 ognl 语法执行一些语句

  • 查看已加载的类的详细信息,比如这个类从哪个 jar 包加载的。也可以查看类的方法的信息

  • dump 某个类的字节码到指定目录

  • 直接反编译指定的类

  • 查看类加载器的一些信息

  • 可以让 jvm 重新加载某个类

  • 监控方法的执行,同时可以获取到执行的入参、出参以及抛出的异常

  • 追踪方法执行的调用栈,以及各个方法的调用时间

有一些命令不会介绍的太详细,因为官方文档已经写的很棒了,我没必要再这复述。想深入了解的朋友也可以直接去看 arthas 的官方文档,中文的,很容易阅读。


一.安装和使用 Arthas


官方文档:https://alibaba.github.io/arthas/install-detail.html

安装

直接通过 java -jar 启动:

wget https://alibaba.github.io/arthas/arthas-boot.jar# 启动后会自动下载响应的lib到 ~/.arthas 目录下java -jar arthas-boot.jar
复制代码

或者直接下载 arthas 的压缩包,然后解压:

unzip arthas-packaging-bin.zip# 执行./as.sh
复制代码

启动 asthas 进程后,它会列出所有的 jvm 进程,并让我们选择要 attach 哪个进程。attach 目标进程后,就进入 athas 的交互命令行了,这时候就可以开始输入 arthas 对应的命令使用了

卸载

 rm -rf ~/.arthas/
复制代码

二.Arthas 的各个命令

1. dashboard

进入当前系统的实时数据面板,按 ctrl+c 退出。这个面板会实时刷新,其中包括线程信息、内存信息、gc 信息、还有一些运行时的数据。

另外,当运行在 Ali-tomcat 时,会显示当前 tomcat 的实时信息,如 HTTP 请求的 qps, rt, 错误数, 线程池信息等等

下面是 arthas 上面的一张 demo 图

在这里插入图片描述

2. thread

通过 thread 命令可以查看当前 jvm 进程的线程详情。可以查看线程的 cpu 使用时间占比,通过指定各种参数可以找出最忙的几个线程,以及阻塞其他线程的线程。具体如何使用这里不多做介绍,大家可以去看 arthas 的官方文档。

3. jvm

通过 jvm 命令直接输出当前 jvm 的各种信息。

4. sysprop 和 sysenv

通过 sysprop 可以查看所有的系统变量,也可以设置某个系统变量。同理,通过 sysenv 可以查看所有的操作系统环境变量,也可以查看设置某个环境变量。

5. getstatic

通过该命令可以查看类的静态属性。不过查看类的静态属性 ognl 命令也可以做到,官方也比较推荐使用 ognl 表达式来做

不过使用 getstatic 可以使用通配符来查看变量,好像用 ognl 不行。(也可能是我对 ognl 表达式了解还不够)

# 查看CommonConstants类下的所有静态属性getstatic *.CommonConstants *
复制代码

6. ognl

通过 ognl 表达式来执行一些语句。

# 调用静态函数ognl '@java.lang.System@out.println("hello")'# 输出null
# 获取静态类的静态字段ognl '@demo.MathGame@random'# 输出@Random[ serialVersionUID=@Long[3905348978240129619], seed=@AtomicLong[125451474443703], multiplier=@Long[25214903917], addend=@Long[11], mask=@Long[281474976710655], DOUBLE_UNIT=@Double[1.1102230246251565E-16], BadBound=@String[bound must be positive], BadRange=@String[bound must be greater than origin], BadSize=@String[size must be non-negative], seedUniquifier=@AtomicLong[-3282039941672302964], nextNextGaussian=@Double[0.0], haveNextNextGaussian=@Boolean[false], serialPersistentFields=@ObjectStreamField[][isEmpty=false;size=3], unsafe=@Unsafe[sun.misc.Unsafe@28ea5898], seedOffset=@Long[24],]
# 执行多行表达式,赋值给临时变量,返回一个Listognl '#value1=@System@getProperty("java.home"),#value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'# 输出@ArrayList[ @String[/opt/java/8.0.181-zulu/jre], @String[OpenJDK Runtime Environment],]
复制代码

ognl 表达式在 arthas 中用的还是比较多的,语法也比较简单。在后面的 monitor、watch、trace、stack 等命令中都会排上用场。

关于 ognl,这个 userCase 上有丰富的案例:

https://github.com/alibaba/arthas/issues/11

7. sc 和 sm

通过 sc 可以查看已加载类的相关信息,比如该类是从哪个 jar 包加载的,被哪个类加载器加载的,以及是否是接口等等。

sm 查看已加载类的方法详情。

8. dump

将已加载类的字节码 dump 到本地磁盘上。

9. jad

反编译已加载的类。让它变成可读的形式。

有时我们经常会不确定线上或者测试环境的包是否是我们修改过的,就可以通过 jad 反编译来看下。

10. classloader

将 JVM 中所有的 classloader 的信息统计出来,并可以展示继承树,urls 等。

11. redefine

该命令可以加载外部的.class文件,然后覆盖 jvm 已加载的类。注意,这个命令不一定都能覆盖成功,如果添加了新的 field,就不会加载成功。

关于 redefine,arthas 的 github 上有个非常经典的 userCase:

https://github.com/alibaba/arthas/issues/263

大体就是作者遇到项目中的日志一直输出[] [] [] No credential found,想要找到是哪个类输出的。由于大多数日志框架输出日志时都用到了 StringBuilder,因此作者对 StringBuilder 的 toString 方法做了以下修改:

    @Override    public String toString() {        // Create a copy, don't share the array        String result = new String(value, 0, count);        if(result.contains("No credential found")) {            System.err.println(result);            new Throwable().printStackTrace();        }        return result;    }
复制代码

改完类之后再用 redefine 把修改后的 StringBuilder 加载进去,这样,当后面继续输出[] [] [] No credential found就可以知道到底是从哪里输出的了。

12. monitor

monitor 命令可以监控方法的执行情况。比如调用成功次数,失败次数,失败率、平均执行时间等等。默认 120 秒输出一次,也就是说,当我们输入 monitor 命令之后,每 120 秒就会输出一次统计结果。

通过-c 参数可以修改输出频率,支持通配符和正则表达式。

13. watch

让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值抛出异常入参,通过编写 OGNL 表达式进行对应变量的查看。

watch 的使用姿势比较丰富,可以在四个不同的场景观察方法的执行。比如方法调用之前方法调用之后方法异常之后方法结束之后。默认观察的是方法结束之后。

如果观察的是方法结束之后的场景,由于入参可能在执行方法时被改变,所以此时输出的可能不是真正的入参。因此,要看真正的入参,要看方法调用之前的,也就是加上-b 的参数。

另外,使用-b 参数观察的话,则观察不到方法返回的结果以及抛出的异常了。

相关参数:


# 观察CommonTest的test方法# 输出 入参、返回结果、抛出的异常 —— 输出的内容可以动态调整# 后面跟着的是 条件表达式,表示耗时超过10ms才输出# -n 表示只执行一次,-x表示 入参和返回结果的展开层次为5层watch *.CommonTest test "{params,returnObj,throwExp}" '#cost>10' -x 5 -n 1
# 耗时大于10ms并且第一个参数等于1才输出watch *.CommonTest test "{params,returnObj,throwExp}" '#cost>10 && params[0]==1' -x 5 -n 1# 第一个参数大于1 并且第二个参数等于hello才输出watch *.CommonTest test "{params,returnObj,throwExp}" 'params[0]>1 && params[1]=="hello"' -x 5 -n 1# 第一个参数小于5或者第二个参数等于"world"就输出watch *.CommonTest test "{params,returnObj,throwExp}" 'params[0]<5 || params[1]=="wolrd"' -x 5 -n 1# 第一个参数的name字段等于world时才输出。# 由于在方法执行过程中参数的name属性可能发生改变,因此加上-b才能观察到真正的入参watch -b *.CommonTest test "{params,returnObj,throwExp}" 'params[0].name=="wolrd"' -x 5 -n 1
# 由于同时指定了-s和-b,所以方法被调用一次,就会输出2次结果(两个场景分开输出),分别是方法被调用前,和返回之后# 注意,这里如果-n只设置成1,那么只会输出-b对应的输出,-s对应的输出由于没有次数了就无法输出了watch *.CommonTest test '{params,returnObj,throwExp}' -x 5 -n 2 -s -b
复制代码

在填写条件表达式时要注意一点,条件表达式中的 params 默认都是获取的方法执行完后的参数信息,比如入参 a 的属性 name 方法执行前是"hello" ,

在方法执行后变成了"world",那么条件表达式传入'params[0].name="hello"'将不会输出,只有填入'params[0].name="hello"'才可以匹配上

这点对于后面的 trace、stack 命令也是一样的。

14. trace

方法内部调用路径,并输出方法路径上的每个节点上耗时。但是该命令只能输出一级调用的方法耗时,往下的就不会输出了。比如我定义了一个类 Test

public class Test{    public void a(){        b();    }    public void b(){        c();    }    public void c(){        //...    }}
复制代码

当我要观察Test a时,trace 命令只会输出b()的耗时,而不会输出c()的耗时。因为对方法 a 来说,只有b()是一级调用。

trace 命令也可以使用条件表达式,来过滤一些不想要的输出

# 方法耗时大于100时才输出,且只输出1.其他条件过滤的语法可以看watch命令的demotrace *.Common* test '#cost>100' -n 1
复制代码

友情提醒下,trace 在执行的过程中本身是会有一定的性能开销,在统计的报告中并未像 JProfiler 一样预先减去其自身的统计开销。所以这统计出来有些许的不准,渲染路径上调用的类、方法越多,性能偏差越大。但还是能让你看清一些事情的。

这里存在一个统计不准确的问题,就是所有方法耗时加起来可能会小于该监测方法的总耗时,这个是由于 Arthas 本身的逻辑会有一定的耗时

15. stack

输出当前方法被调用的调用路径。用法和 trace 差不多

# 方法耗时大于100时才输出,且只输出1.其他条件过滤的语法可以看watch命令的demostack *.Common* test '#cost>100' -n 1
复制代码

16. tt

tt 命令会记录每次方法调用的各种信息。它和 watch 有些相似但是它能记录下各个时间点的调用信息,之后随时查看,甚至 replay 这次调用。

在这里插入图片描述

从上图中我们可以看到,通过-i参数我们可以直接查看之前某次调用的详细信息。

# 指定第一个参数的mobile字段等于13989838402tt -t *.CommonTest test params[0].mobile=="13989838402"# 把之前记录的那些调用都输出来tt -l# 
复制代码

replay 某次调用:

# replay时间片的index=1002这次调用tt -i 1002 -p
复制代码

使用 tt 进行方法 replay 时,要注意 2 点

17. options


options 用来打开关闭某些功能:


用户头像

我们始于迷惘,终于更高水平的迷惘。 2020.03.25 加入

🏆 【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 🤝未来我们希望可以共同进步🤝

评论 (1 条评论)

发布
用户头像
加油

2021 年 05 月 12 日 23:57
回复
没有更多了
Arthas-技术专题-使用指南