Arthas 的使用

October 24, 2023 作者: dyzmj 分类: 工具 浏览: 5 评论: 0

一、Arthas 能做什么

引入一段官方的描述:

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

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

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

二、安装与启动

2.1 推荐使用 arthas-boot

下载 arthas-boot.jar,然后用 java -jar 的方式启动:

curl -O https://arthas.aliyun.com/arthas-boot.jar

java -jar arthas-boot.jar

2.2 脚本一键安装

使用 as.sh,Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,复制以下内容,敲 回车 执行即可:

curl -L https://arthas.aliyun.com/install.sh | sh

2.3 退出

不用 arthas 时一定要正常退出,命令如下:

  • quit: 只是退出当前的连接。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时执行 java -jar arthas-boot.jar 可以直接连接上。
  • exit:quit 命令一样的功能。
  • stop: 完全退出 arthas

注意:

arthas 依赖 JDK 的环境变量,也依赖一些 JDK 自带的工具,比如 jps,如果服务器上只有 JRE 环境而没有 JDK 环境的话,是没有 jps 的,所以 arthas 也会报错。

三、快速入门

3.1 进入指定的 JVM 进程

在命令行中执行(使用和目标进程一致的用户启动,否则可能 attach 失败):

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

注:

  • 如果下载速度比较慢,可以使用 aliyun 的镜像: java -jar arthas-boot.jar --repo-mirror aliyun --use-http
  • 可以使用 java -jar arthas-boot.jar -h 打印更多参数信息。

执行完后可以看到:

image-20220525135015568

如上,如系统内含有多个 Java 程序,可以使用 ps -ef|grep java 查找要查看程序的 PID,输入上面 PID 对应的序号即可进入 arthas 中。

image-20220525135645074

3.2 基础命令

3.2.1 查看 dashboard 仪表盘

输入 dashboard命令,会在仪表盘中展示当前进程的信息,按 ctrl+c/q 可以中断执行。

image-20220525145001099

数据列说明:

  • ID: Java 级别的线程 ID,注意这个ID 不能跟 jstack 中的 nativeID 一一对应。

  • NAME: 线程名。

  • GROUP: 线程组名。

  • PRIORITY: 线程优先级,1~10 之间的数字,越大表示优先级越高。

  • STATE: 线程的状态。

  • %CPU: 线程的 cpu 使用率。比如采样间隔时间 1000ms,某个线程的增量 cpu 时间为 100ms,则 cpu 使用率 = 100 / 1000 = 10%。

  • DELTA_TIME: 上次采样之后线程运行增量 cpu 时间,数据格式为

  • TIME: 线程运行总 cpu 时间,数据格式为 分:秒

  • INTERRUPTED: 线程当前的中断位状态。

  • DAEMON: 是否是 daemon 线程。

JVM 内部线程:

Java 8 之后支持获取 JVM 内部线程 CPU 时间, 这些线程只有名称和 CPU 时间,没有 ID 及状态等信息(显示 ID 为 -1)。通过内部线程可以观测到 JVM 活动,如 GC、JIT 编译等占用 CPU 的情况,方便了解 JVM 整体运行状况。

  • 当 JVM 堆(heap)/ 元数据(metaspace)空间不足或 OOM 时,可以看到 GC 线程 CPU 占用率明细高于其他的线程。
  • 当执行 trace/watch/tt/redefine 等命令后,可以看到 JIT 线程活动变得更频繁。因为 JVM 热更新 class 字节码时清除了此 class 相关的 JIT 编译结果,需要重新编译。

JVM内部线程包括一下几种:

  • JIT 编译线程:C1 CompilerThread0C2 CompilerThread0
  • GC 线程:GC Thread0G1 Young RemSet Sampling
  • 其他内部线程:VM Periodic Task ThreadVM ThreadService Thread

设置刷新间隔和次数:

面板默认会每 5 秒刷新一次,并且会一直刷新下去,如果想要指定刷新次数和间隔时间,可以这么写:

dashboard -i 2000 -n 2    // 间隔两秒刷新两次

-i 表示刷新的间隔时间,单位毫秒,-n 表示查询的次数,到达指定次数后,自动退出仪表盘面板。

3.2.2 thread 查看线程堆栈信息

当没有参数,默认按照CPU增量时间降序排列,只显示第一页数据:thread

image-20220525151932847

查看某个线程的堆栈:thead 1,1 一般为 main 线程的线程 id。

image-20220525152337855

除此之外,thread 还有其他的用法:

  • thread -n 5 :打印前 5 个最忙的线程并打印堆栈。
  • thread -all :显示所有匹配的线程。
  • thread -n 3 -i 1000:列出 1000ms 内最忙的 3 个线程。
  • thread -state WAITTING:查看指定状态的线程(TIMED_WAITWAITINGRUNNABLE 等)。
  • thread -b:找出阻塞其他线程的线程,当出现死锁后,会提示出现死锁的位置。

3.2.3 通过 jad 来反编译 Class

使用 jad 类路径名 即可反编译指定的类。

[arthas@16616]$ jad demo.MathGame

ClassLoader:                                                                                         
+-sun.misc.Launcher$AppClassLoader@5c647e05                                                                                                                                             
  +-sun.misc.Launcher$ExtClassLoader@77683676                                                                                                                                           
Location:                                                                                                                                                                                                         
/usr/local/soft/yudd/test/math-game.jar                                                     
       /*
        * Decompiled with CFR.
        */
       package demo;
       
       import java.util.ArrayList;
       import java.util.List;
       import java.util.Random;
       import java.util.concurrent.TimeUnit;
       
       public class MathGame {
           private static Random random = new Random();
           private int illegalArgumentCount = 0;
       
           public List<Integer> primeFactors(int number) {
/*44*/         if (number < 2) {
/*45*/             ++this.illegalArgumentCount;
                   throw new IllegalArgumentException("number is: " + number + ", need >= 2");
               }
               ArrayList<Integer> result = new ArrayList<Integer>();
/*50*/         int i = 2;
/*51*/         while (i <= number) {
/*52*/             if (number % i == 0) {
/*53*/                 result.add(i);
/*54*/                 number /= i;
/*55*/                 i = 2;
                       continue;
                   }
/*57*/             ++i;
               }
/*61*/         return result;
           }
       
           public static void main(String[] args) throws InterruptedException {
               MathGame game = new MathGame();
               while (true) {
/*16*/             game.run();
/*17*/             TimeUnit.SECONDS.sleep(1L);
               }
           }
       
           public void run() throws InterruptedException {
               try {
/*23*/             int number = random.nextInt() / 10000;
/*24*/             List<Integer> primeFactors = this.primeFactors(number);
/*25*/             MathGame.print(number, primeFactors);
               }
               catch (Exception e) {
/*28*/             System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
               }
           }
       
           public static void print(int number, List<Integer> primeFactors) {
               StringBuffer sb = new StringBuffer(number + "=");
/*34*/         for (int factor : primeFactors) {
/*35*/             sb.append(factor).append('*');
               }
/*37*/         if (sb.charAt(sb.length() - 1) == '*') {
/*38*/             sb.deleteCharAt(sb.length() - 1);
               }
/*40*/         System.out.println(sb);
           }
       }

Affect(row-cnt:1) cost in 793 ms.

此命令反编译的内容包括类使用的类加载器、所在的 jar 包、源码信息。

若要将反编译的源码生成到指定的文件中可使用如下命令:

jad --source-only demo.MathGame  > /home/MathGame.java

3.2.4 watch 查看方法的出入参

通过 watch 命令来查看 demo.MathGame#primeFactors方法的返回值:

image-20220525171013375

参数说明:

  • "{params, returnObj}" :观察表达式,默认值:{params, target, returnObj},是一个 OGNL 表达式。
  • -x 3:是指定输出结果的属性遍历深度,默认值为1,为1时看不到参数化的具体值,只能看到类型,最大值为4,防止展开结果占用太多内存。可以在 OGNL 表达式里指定更具体的 field
  • -b:在方法调用之前观察,用此命令可查看方法的入参。
  • -e:在方法异常之后观察,用此命令可以查看方法抛出的异常。
  • -s:在方法返回之后观察,可查看方法的返回值。
  • -f:在方法结束之后(正常返回和异常返回)观察,可查看方法的返回值和异常信息,默认打开 -f
  • -n 4:表示监控方法只执行四次。

3.3 退出 arthas

如果只是退出当前的连接,可以使用 quitexit命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。

如果想要完全退出 arthas,可以执行 stop 命令。

四、进阶使用

4.1 基础命令

命令描述
help查看命令帮助信息
cat打印文件内容,和 linux 里面的 cat 命令类似
echo打印参数,和 linux 里面的 echo 命令类似
grep匹配查找,和 linux 里面的 grep 命令类似
base64base64 编码转换,和 linux 里面的 base64 命令类似
tee复制标准输入到标准输出和指定的文件,和 linux 里的 tee 命令类似
pwd返回当前的工作目录,和 linux pwd 命令类似
cls清空当前屏幕区域
session查看当前会话的信息
rest重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
version输出当前目标 Java 进程所加载的 Arthas 版本号
history打印命令历史
quit退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
stop关闭 Arthas 服务端,所有 Arthas 客户端全部退出
keymapArthas快捷键列表及自定义快捷键

4.2 JVM 相关指令

4.2.1 dashboard

查看当前系统的实时数据面板,按 q 或 ctrl +c 退出。

image-20220526095525123

从上至下分别为:

  • 线程相关

    • ID:线程ID
    • NAME:线程名字
    • GROUP:线程组
    • PRIORITY:线程优先级
    • STATE:线程状态
    • %CPU:cpu占比
    • TIME:运行时间。分钟:秒
    • INTERRUPTE:中断状态
    • DAEMON:是否是守护线程
  • 内存相关

    • Memroy:内存区域
    • used:使用的内存
    • total:总内存
    • max:最大内存
    • usage:使用百分比
    • GC:垃圾回收机制
  • 运行环境

4.2.2 thread

查看当前线程信息,查看线程的堆栈。

  • 数字:线程ID

image-20220526102016622

  • n:前 n 个最忙的线程堆栈信息

image-20220526102145907

  • b:找出当前线程阻塞其他线程的线程

image-20220526102250775

  • i :制定 CPU 占比统计采样间隔,单位为毫秒

  • 查看某种状态的所有线程

image-20220526102452565

4.2.3 jvm

查看当前 JVM 信息。

image-20220526103155732

THREAD相关

  • COUNT: JVM当前活跃的线程数
  • DAEMON-COUNT: JVM当前活跃的守护线程数
  • PEAK-COUNT: 从JVM启动开始曾经活着的最大线程数
  • STARTED-COUNT: 从JVM启动开始总共启动过的线程次数
  • DEADLOCK-COUNT: JVM当前死锁的线程数

文件描述符相关

  • MAX-FILE-DESCRIPTOR-COUNT:JVM进程最大可以打开的文件描述符数
  • OPEN-FILE-DESCRIPTOR-COUNT:JVM当前打开的文件描述符数

4.2.4 sysprop

查看 Java 虚拟机属性,可以修改。

image-20220526103516258

4.2.5 sysenv

查看 JVM 环境属性。

image-20220526103655940

4.2.6 vmoption

查看、更新 JVM 诊断相关参数。

image-20220526103751786

4.2.7 perfcounter

查看当前JVM 的 Perf Counter 信息

image-20220526103935203

4.2.8 logger

查看 logger 信息,更新 logger level。

image-20220526104150777

4.2.9 getstatic

获取静态属性,getstatic 类名 属性名

image-20220526104326828

4.2.10 ognl

执行 OGNL 表达式,需要学习 OGNL 语法。

使用参考:

  1. 《OGNL 特殊用法参考》
  2. 《OGNL 表达式官方指南》
  • 调用静态方法

image-20220526105522407

  • 获取静态字段

image-20220526105734323

  • 执行多行表达式,赋值给临时变量,返回一个List

image-20220526105840506

4.2.11 mbean

查看 Mbean 的信息。

image-20220526110319266

4.2.12 heapdump

dump java heap,类似 jmap 命令的 heap dump 功能。

image-20220526110909563

4.2.13 vmtool

vmtool 利用 JVMTI 接口,实现查询内存对鞋,强制 GC 等功能。

  • 获取对象

image-20220526111132791

注:通过 --limit参数,可以限制返回值数量,避免获取超大数据时对JVM造成压力。默认值是10。

  • 强制 GC

image-20220526111307879

4.3 class/classloader 相关

4.3.1 sc

查看 JVM 已加载的类信息。

“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息。

参数说明

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

image-20220526112951102

4.3.2 sm

查看已加载类的方法信息。

“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。

参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法名表达式匹配
[d]展示每个方法的详细信息
[E]开启正则表达式匹配,默认为通配符匹配
[c:]指定class的 ClassLoader 的 hashcode
[classLoaderClass:]指定执行表达式的 ClassLoader 的 class name
[n:]具有详细信息的匹配类的最大数量(默认为100)

image-20220526113356145

4.3.3 jad

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

反编译某个类:

image-20220526113551002

反编译某个方法:

image-20220526113623985

4.3.4 mc

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

mc /tmp/Test.java

可以通过 -c 参数指定 classloader:

mc -c 5c647e05 /home/MathGame.java

可以通过 -d 命令指定输出目录:

mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java

编译生成 .class 文件之后,可以结合 retransformredefine 命令实现热更新代码。

注意:mc 命令有可能失败。如果编译失败可以在本地编译好 .class 文件,再上传到服务器。

image-20220526133547624

4.3.5 retransform

加载外部的 .class 文件,retransform jvm 已加载的类。

retransform指定的 .class 文件:

image-20220526133801950

加载指定的 .class 文件,然后解析出 class name,在 retransform jvm 中已加载的对应的类。每加载一个 .class 文件,则会记录一个 retransform entry。

如果多次执行 retransform 加载同一个 class 文件,则会有多条 retransform entry。

查看 retransform entry:

image-20220526134153815

删除指定 retransform entry:

需要指定 id:

image-20220526134345234

显示触发 retransform:

image-20220526134525734

注意:对于同一个类,当存在多个 retransform entry 时,如果显式触发 retransform,则最后添加的 entry 生效(id 最大的)。

消除 retransform 的影响:

如果对某个类执行 retransform 之后,想消除影响,则需要:

  • 删除这个类对应的 retransform entry
  • 重新触发 retransform

如果不清除掉所有的 retransform entry,并重新触发 retransform,则 arthas stop 时,retransform 过时的类仍然生效。

retransform 使用限制:

  • 不允许新增加 filed/method
  • 正在运行的方法,一直没有退出不能生效。

4.3.6 redefine

加载外部的 .class 文件,redefine jvm 已加载的类。

常见问题:

推荐使用 retransform 命令。

  • redefine的class不能修改、添加、删除类的field和method,包括方法参数、方法名称及返回值
  • 如果mc失败,可以在本地开发环境编译好class文件,上传到目标系统,使用redefine热加载class
  • 目前redefine 和watch/trace/jad/tt等命令冲突

注意:redefine 后的原来的类不能恢复,redefine 有可能失败(比如增加了新的 field),可参考 JDK 本身的文档。

reset 命令对 redefine 的类无效。如果想重置,则需要 redefine 原始的字节码。

redefine 命令 和 jadwatchtracemonitortt 等命令会冲突,执行完 redefine 之后,执行这个命令会把 redefine 的字节码重置。

redefine限制:

  • 不允许新增加 filed/method
  • 正在运行的方法,一直没有退出不能生效。

4.3.7 dump

dump 已加载类的 bytecode 到特定目录。

image-20220526141258818

4.3.8 classloader

查看 classloader 的继承树、urls、类加载信息。

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

可以让指定的 classloader 去 getResources,打印出所有查找到的resources的url。对于ResourceNotFoundException比较有用。

参数说明:

参数名称参数说明
[l]按类加载实例进行统计
[t]打印所有 ClassLoader 的继承树
[a]列出所有 ClassLoader 加载的类,请谨慎使用
[c:]ClassLoader 的 hashcode
[classLoaderClass:]指定执行表达式的 ClassLoader 的 class name
[c: r:]用 ClassLoader 去查找 resource
[c: load:]用 ClassLoader 去加载指定的类

image-20220526141902960

4.4 monitor/watch/trace 相关

4.4.1 monitor

方法执行监控,非实时返回命令(输入命令后,一直等待目标 Java 进程返回信息,直到用户输入 ctrl + c 为止)。

监控的维度说明:

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

参数说明:

参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法名表达式匹配
condition-express条件表达式
[E]开启正则表达式匹配,默认为通配符匹配
[c:]统计周期,默认值为120秒
[b]方法调用之前计算condition-express

image-20220526143229555

4.4.2 watch

方法执行数据观测。

在诊断程序过程中,使用比较多的。检测方法的执行情况,入参、返回值、异常、出参等,并且可以使用ognl查看对应变量的值(因为支持ognl表达式,能够看到方法内部执行的情况)。

参数说明:

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

说明:

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

使用案例:

1、监控方法出参和返回值,属性遍历深度为2

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

image-20220526145719254

2、观察方法的入参,因为观察点是执行前,所以只能看到入参,无法看到返回值

watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b

image-20220526145835607

3、观察调用方法的对象属性,查看方法执行前后,当前对象中的属性,可以使用traget关键字,表示当前对象

watch demo.MathGame primeFactors "target" -x 2 -b

image-20220526150015069

4、查看对象的属性

watch demo.MathGame primeFactors "target.illegalArgumentCount" -x 2 -b

image-20220526150129316

5、方法调用前后,参数的不同,执行2次

watch demo.MathGame primeFactors "{params, target, returnObj}" -x 2 -b -s -n 2

image-20220526150304880

6、查看第一个参数小于 0 的情况

watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0" -x 2 -b

image-20220526150440231

4.4.3 trace

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

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

参数说明:

参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法名表达式匹配
condition-express条件表达式
[E]开启正则表达式匹配,默认为通配符匹配
[n:]命令执行次数
#cost方法执行耗时

注意事项:

  • trace 能方便的帮助你定位和发现因 RT 高而导致的性能问题缺陷,但其每次只能跟踪一级方法的调用链路。
  • 3.3.0 版本后,可以使用动态Trace功能,不断增加新的匹配类。
  • 目前不支持 trace java.lang.Thread getName

使用案例:

1、查看某个方法的运行情况,只捕获2次的运行结果。

trace demo.MathGame run -n 2

image-20220526150920530

2、跳过 JDK 执行的方法。

trace --skipJDKMethod false demo.MathGame run -n 2

image-20220526151042667

3、对耗时进行筛选。

trace demo.MathGame run -n 2 '#cost > 5'

image-20220526151351390

4.4.4 stack

输出当前方法被调用的调用路径。

很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。

参数说明:

参数名称参数说明
class-pattern类名表达式匹配
method-pattern方法名表达式匹配
condition-express条件表达式
[E]开启正则表达式匹配,默认为通配符匹配
[n:]执行次数限制

使用案例:

1、查看方法的输出路径,2次。

stack demo.MathGame primeFactors -n 2

image-20220526151931640

2、条件表达式,第 0 个参数小于 0。

stack demo.MathGame primeFactors "params[0] < 0" -n 2

image-20220526152052700

3、耗时条件。

stack demo.MathGame primeFactors "params[0] < 0 and #cost > 0.0005" -n 2

image-20220526152319281

4.4.5 tt

方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测。

watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。

这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。

于是乎,TimeTunnel 命令就诞生了。

参数说明:

tt 的参数说明
-t记录某个方法在一个时间段中的对调用
-l显示所有已经记录的列表
-n 次数只记录多少次
-s 表达式搜索表达式
-i 索引号查看指定索引号的详细调用信息
-p重新调用指定的索引号时间碎片
  • 支持条件表达式

  • 解决方法重载

    • tt -t *Test print params.length==1
    • tt -t *Test print 'params[1] instanceof Integer'
  • ​ 指定参数值

    • tt -t *Test print params[0].mobile=="13989838402"

使用案例:

1、调用某个方法的时间隧道。

tt -t demo.MathGame primeFactors

image-20220526155831225

上图表格字段说明:

表格字段字段解释
INDEX时间片段记录编号,每一个编号代表着一次调用,后续tt还有很多命令都是基于此编号指定记录操作,非常重要。
TIMESTAMP方法执行的本机时间,记录了这个时间片段所发生的本机时间
COST(ms)方法执行的耗时
IS-RET方法是否以正常返回的形式结束
IS-EXP方法是否以抛异常的形式结束
OBJECT执行对象的hashCode(),注意,曾经有人误认为是对象在JVM中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体
CLASS执行的类名
METHOD执行的方法名

2、检索时间片段,显示之前记录的全部时间隧道

tt -l

image-20220526160033686

tt -s 'method.name=="primeFactors"'

image-20220526160201007

tt -i 1007 查看索引为某次的调用

image-20220526160234054

tt -i 1007 --replay-times 3 --replay-interval 2 重新调用

image-20220526160419866

需要强调的点:

  1. ThreadLocal 信息丢失

很多框架偷偷的将一些环境变量信息塞到了发起调用线程的 ThreadLocal 中,由于调用线程发生了变化,这些 ThreadLocal 线程信息无法通过 Arthas 保存,所以这些信息将会丢失。

  1. 引用的对象

需要强调的是,tt 命令是将当前环境的对象引用保存起来,但仅仅也只能保存一个引用而已。如果方法内部对入参进行了变更,或者返回的对象经过了后续的处理,那么在 tt 查看的时候将无法看到当时最准确的值。这也是为什么 watch 命令存在的意义。

4.5 profiler 火焰图

使用 async-profiler 生成火焰图。

参数说明:

参数名称参数说明
action要执行的操作
actionArg属性名模式
[i:]采样间隔(单位:ns)(默认值:10'000'000,即10 ms)
[f:]将输出转储到指定路径
[d:]运行评测指定秒
[e:]要跟踪哪个事件(cpu, alloc, lock, cache-misses等),默认是cpu

生成火焰图的操作命令:

操作命令说明
profiler start启动 profiler,默认生成 CPU 的火焰图
profiler list显示所有支持的事件
profiler getSamples获取已采集的 sample 数量
profiler status查看 profiler 的状态,运行的时间
profiler stop停止 profiler ,生成火焰图的结果,指定输出目录和输出格式,svg或html

image-20220526165320776

火焰图的查看方法参考:

五、使用案例

5.1 确定哪个 Controller 处理了请求

trace org.springframework.web.servlet.DispatcherServlet *

image-20220526170544324

jad org.springframework.web.servlet.DispatcherServlet doDispatch 可以查看到方法 getHandler

image-20220526170944455

watch org.springframework.web.servlet.DispatcherServlet getHandler '{params, returnObj}' -x 2

image-20220526171706171

5.2 指定方法调用的参数和返回值是多少

watch com.goldcard.nbiot.web.home.controller.device.DeviceController replaceDevice '{params,returnObj,throwExp}' -n 5 -x 3

image-20220526171923434

5.3 热更新代码

1、jad 命令,将需要更改的文件先进行翻编译,保存下来,编译器修改 jad --source-only com.example.DemoApplication > /data/DemoApplication.java

2、sc 命令,查找当前类是哪个 classLoader 加载的

sc -d *DempApplication | grep classLoader

3、mc 命令,用指定的classLoader重新将类在内存中编译,若此步错误,可以使用 IDE 编译,然后将编译好的 .class 文件上传至服务器。

mc -c 20ad9418 /data/DemoApplication.java -d /data

4、retransform 命令,将编译后的类加载到JVM上(这里推荐使用 retransform 命令而不是 redefine命令,redefine命令会将原 .class 文件覆盖且不可还原,而且与 watchtrace命令也有冲突)。

retransform /data/com/example/DemoApplication.class

5.4 使用 IDEA 插件

推荐使用 IDEA 中的 《arthas idea》插件,选择类或方法,点击邮件即可打开:

image-20220526173440620

5.5 更多案例

六、附录

6.1 arthas 命令速查手册

image-20220526142218804

#Arthas(1)#Java(6)

评论