Arthas 的使用
一、Arthas 能做什么
引入一段官方的描述:
当你遇到一下类似问题而束手无策是,
Arthas
可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit ?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法复现!
- 是否有一个全局视角来查看系统的运行情况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从 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
打印更多参数信息。
执行完后可以看到:
如上,如系统内含有多个 Java 程序,可以使用 ps -ef|grep java
查找要查看程序的 PID
,输入上面 PID
对应的序号即可进入 arthas
中。
3.2 基础命令
3.2.1 查看 dashboard 仪表盘
输入 dashboard
命令,会在仪表盘中展示当前进程的信息,按 ctrl+c/q
可以中断执行。
数据列说明:
-
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 CompilerThread0
、C2 CompilerThread0
。GC 线程:
如GC Thread0
,G1 Young RemSet Sampling
。其他内部线程:
如VM Periodic Task Thread
、VM Thread
、Service Thread
。
设置刷新间隔和次数:
面板默认会每 5 秒刷新一次,并且会一直刷新下去,如果想要指定刷新次数和间隔时间,可以这么写:
dashboard -i 2000 -n 2 // 间隔两秒刷新两次
-i
表示刷新的间隔时间,单位毫秒,-n
表示查询的次数,到达指定次数后,自动退出仪表盘面板。
3.2.2 thread 查看线程堆栈信息
当没有参数,默认按照CPU增量时间降序排列,只显示第一页数据:thread
。
查看某个线程的堆栈:thead 1
,1 一般为 main 线程的线程 id。
除此之外,thread
还有其他的用法:
thread -n 5
:打印前 5 个最忙的线程并打印堆栈。thread -all
:显示所有匹配的线程。thread -n 3 -i 1000
:列出 1000ms 内最忙的 3 个线程。thread -state WAITTING
:查看指定状态的线程(TIMED_WAIT
、WAITING
、RUNNABLE
等)。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
方法的返回值:
参数说明:
"{params, returnObj}"
:观察表达式,默认值:{params, target, returnObj}
,是一个 OGNL 表达式。-x 3
:是指定输出结果的属性遍历深度,默认值为1,为1时看不到参数化的具体值,只能看到类型,最大值为4,防止展开结果占用太多内存。可以在OGNL
表达式里指定更具体的field
。-b
:在方法调用之前观察,用此命令可查看方法的入参。-e
:在方法异常之后观察,用此命令可以查看方法抛出的异常。-s
:在方法返回之后观察,可查看方法的返回值。-f
:在方法结束之后(正常返回和异常返回)观察,可查看方法的返回值和异常信息,默认打开-f
。-n 4
:表示监控方法只执行四次。
3.3 退出 arthas
如果只是退出当前的连接,可以使用 quit
或 exit
命令。Attach 到目标进程上的 arthas
还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想要完全退出 arthas
,可以执行 stop
命令。
四、进阶使用
4.1 基础命令
命令 | 描述 |
---|---|
help | 查看命令帮助信息 |
cat | 打印文件内容,和 linux 里面的 cat 命令类似 |
echo | 打印参数,和 linux 里面的 echo 命令类似 |
grep | 匹配查找,和 linux 里面的 grep 命令类似 |
base64 | base64 编码转换,和 linux 里面的 base64 命令类似 |
tee | 复制标准输入到标准输出和指定的文件,和 linux 里的 tee 命令类似 |
pwd | 返回当前的工作目录,和 linux pwd 命令类似 |
cls | 清空当前屏幕区域 |
session | 查看当前会话的信息 |
rest | 重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类 |
version | 输出当前目标 Java 进程所加载的 Arthas 版本号 |
history | 打印命令历史 |
quit | 退出当前 Arthas 客户端,其他 Arthas 客户端不受影响 |
stop | 关闭 Arthas 服务端,所有 Arthas 客户端全部退出 |
keymap | Arthas 快捷键列表及自定义快捷键 |
4.2 JVM 相关指令
4.2.1 dashboard
查看当前系统的实时数据面板,按 q 或 ctrl +c 退出。
从上至下分别为:
-
线程相关
- ID:线程ID
- NAME:线程名字
- GROUP:线程组
- PRIORITY:线程优先级
- STATE:线程状态
- %CPU:cpu占比
- TIME:运行时间。分钟:秒
- INTERRUPTE:中断状态
- DAEMON:是否是守护线程
-
内存相关
- Memroy:内存区域
- used:使用的内存
- total:总内存
- max:最大内存
- usage:使用百分比
- GC:垃圾回收机制
-
运行环境
4.2.2 thread
查看当前线程信息,查看线程的堆栈。
- 数字:线程ID
- n:前 n 个最忙的线程堆栈信息
- b:找出当前线程阻塞其他线程的线程
-
i :制定 CPU 占比统计采样间隔,单位为毫秒
-
查看某种状态的所有线程
4.2.3 jvm
查看当前 JVM 信息。
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 虚拟机属性,可以修改。
4.2.5 sysenv
查看 JVM 环境属性。
4.2.6 vmoption
查看、更新 JVM 诊断相关参数。
4.2.7 perfcounter
查看当前JVM 的
Perf Counter
信息
4.2.8 logger
查看 logger 信息,更新 logger level。
4.2.9 getstatic
获取静态属性,
getstatic 类名 属性名
。
4.2.10 ognl
执行 OGNL 表达式,需要学习 OGNL 语法。
使用参考:
- 调用静态方法
- 获取静态字段
- 执行多行表达式,赋值给临时变量,返回一个List
4.2.11 mbean
查看 Mbean 的信息。
4.2.12 heapdump
dump java heap,类似
jmap
命令的heap dump
功能。
4.2.13 vmtool
vmtool
利用JVMTI
接口,实现查询内存对鞋,强制 GC 等功能。
- 获取对象
注:通过
--limit
参数,可以限制返回值数量,避免获取超大数据时对JVM造成压力。默认值是10。
- 强制 GC
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) |
4.3.2 sm
查看已加载类的方法信息。
“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 展示每个方法的详细信息 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[c:] | 指定class的 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[n:] | 具有详细信息的匹配类的最大数量(默认为100) |
4.3.3 jad
反编译指定已加载类的源码。
反编译某个类:
反编译某个方法:
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
文件之后,可以结合 retransform
或 redefine
命令实现热更新代码。
注意:
mc
命令有可能失败。如果编译失败可以在本地编译好.class
文件,再上传到服务器。
4.3.5 retransform
加载外部的
.class
文件,retransform jvm 已加载的类。
retransform指定的 .class 文件:
加载指定的 .class
文件,然后解析出 class name,在 retransform jvm 中已加载的对应的类。每加载一个 .class
文件,则会记录一个 retransform entry。
如果多次执行
retransform
加载同一个 class 文件,则会有多条 retransform entry。
查看 retransform entry:
删除指定 retransform entry:
需要指定 id:
显示触发 retransform:
注意:对于同一个类,当存在多个 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
命令 和jad
、watch
、trace
、monitor
、tt
等命令会冲突,执行完redefine
之后,执行这个命令会把redefine
的字节码重置。
redefine限制:
- 不允许新增加
filed/method
。 - 正在运行的方法,一直没有退出不能生效。
4.3.7 dump
dump
已加载类的bytecode
到特定目录。
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 去加载指定的类 |
4.4 monitor/watch/trace 相关
4.4.1 monitor
方法执行监控,非实时返回命令(输入命令后,一直等待目标 Java 进程返回信息,直到用户输入
ctrl + c
为止)。
监控的维度说明:
监控项 | 说明 |
---|---|
timestamp | 时间戳 |
class | Java类 |
method | 方法(构造方法、普通方法) |
total | 调用次数 |
success | 成功次数 |
fail | 失败次数 |
rt | 平均RT |
fail-rate | 失败率 |
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[c:] | 统计周期,默认值为120秒 |
[b] | 在方法调用之前计算condition-express |
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
有三种可能值:AtEnter
,AtExit
,AtExceptionExit
。对应函数入口,函数正常return,函数抛出异常。
使用案例:
1、监控方法出参和返回值,属性遍历深度为2
watch demo.MathGame primeFactors "{params,returnObj}" -x 2
2、观察方法的入参,因为观察点是执行前,所以只能看到入参,无法看到返回值
watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b
3、观察调用方法的对象属性,查看方法执行前后,当前对象中的属性,可以使用traget关键字,表示当前对象
watch demo.MathGame primeFactors "target" -x 2 -b
4、查看对象的属性
watch demo.MathGame primeFactors "target.illegalArgumentCount" -x 2 -b
5、方法调用前后,参数的不同,执行2次
watch demo.MathGame primeFactors "{params, target, returnObj}" -x 2 -b -s -n 2
6、查看第一个参数小于 0 的情况
watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0" -x 2 -b
4.4.3 trace
方法内部调用路径,并输出方法路径上的每个节点耗时。
trace
命令能主动搜索 class-pattern
/method-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
2、跳过 JDK 执行的方法。
trace --skipJDKMethod false demo.MathGame run -n 2
3、对耗时进行筛选。
trace demo.MathGame run -n 2 '#cost > 5'
4.4.4 stack
输出当前方法被调用的调用路径。
很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] | 执行次数限制 |
使用案例:
1、查看方法的输出路径,2次。
stack demo.MathGame primeFactors -n 2
2、条件表达式,第 0 个参数小于 0。
stack demo.MathGame primeFactors "params[0] < 0" -n 2
3、耗时条件。
stack demo.MathGame primeFactors "params[0] < 0 and #cost > 0.0005" -n 2
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
上图表格字段说明:
表格字段 | 字段解释 |
---|---|
INDEX | 时间片段记录编号,每一个编号代表着一次调用,后续tt还有很多命令都是基于此编号指定记录操作,非常重要。 |
TIMESTAMP | 方法执行的本机时间,记录了这个时间片段所发生的本机时间 |
COST(ms) | 方法执行的耗时 |
IS-RET | 方法是否以正常返回的形式结束 |
IS-EXP | 方法是否以抛异常的形式结束 |
OBJECT | 执行对象的hashCode() ,注意,曾经有人误认为是对象在JVM中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体 |
CLASS | 执行的类名 |
METHOD | 执行的方法名 |
2、检索时间片段,显示之前记录的全部时间隧道
tt -l
tt -s 'method.name=="primeFactors"'
tt -i 1007
查看索引为某次的调用
tt -i 1007 --replay-times 3 --replay-interval 2
重新调用
需要强调的点:
- ThreadLocal 信息丢失
很多框架偷偷的将一些环境变量信息塞到了发起调用线程的 ThreadLocal 中,由于调用线程发生了变化,这些 ThreadLocal 线程信息无法通过 Arthas 保存,所以这些信息将会丢失。
- 引用的对象
需要强调的是,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 |
火焰图的查看方法参考:
五、使用案例
5.1 确定哪个 Controller 处理了请求
trace org.springframework.web.servlet.DispatcherServlet *
jad org.springframework.web.servlet.DispatcherServlet doDispatch
可以查看到方法 getHandler
watch org.springframework.web.servlet.DispatcherServlet getHandler '{params, returnObj}' -x 2
5.2 指定方法调用的参数和返回值是多少
watch com.goldcard.nbiot.web.home.controller.device.DeviceController replaceDevice '{params,returnObj,throwExp}' -n 5 -x 3
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
文件覆盖且不可还原,而且与 watch
、trace
命令也有冲突)。
retransform /data/com/example/DemoApplication.class
5.4 使用 IDEA 插件
推荐使用 IDEA 中的 《arthas idea》插件,选择类或方法,点击邮件即可打开:
5.5 更多案例
六、附录
6.1 arthas 命令速查手册
#Arthas(1)#Java(6)评论