1、Arthas(阿尔萨斯)简介
Arthas 是Alibaba开源的Java诊断工具,当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从JVM内查找某个类的实例?
上面问题中标黄色的三个问题是我实际工作中遇到的问题,感受颇深。
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式(类似cli工具),同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
关于Arthas学习和快速上手,Arthas 用户文档官网文档写的很详细(还包括在线教程,真的只需要看官网就可以了),因为是Alibaba出品所以英文差的同学也能无缝衔接。参考博客中的第二个B站视频也是参考的官方文档的(大纲几乎都一模一样),我这里写一个学习笔记算是加深一下印象,Arthas这类问题诊断工具还是要多实战多解决线上或者测试问题才能灵活深入掌握。
2、Linux下安装和卸载Arthas
所有下载安装姿势都在官网这里:Arthas Install,生产环境和测试环境出于安全考虑,通常不会连公网,而是走的内网,因此直接curl命令阿里云的资源显然不可能,这时候我们可以使用离线安装的方式。
2.1 安装
(1)maven仓库下载Arthas的zip包
Arthas官网上有跳转,直接下载即可,或者去maven仓库中下载。
(2)上传Arthas的zip包
可以选择任意的ftp工具将第一步得到的zip包上传至环境中,可以为Arthas专门创建一个目录,如下:
unzip解压命令:
unzip -d 解压到的目录 zip文件名
上图中蓝色矩形框中的arthas-boot.jar很重要,我们可以通过启动jar包的方式开启Arthas这个java进程,也就是说Arthas这个工具的交付件是一个jar包,启动后就是一个Arthas的Java进程。
(3)启动arthas-boot.jar
首先在环境中安装JDK,方法见:阿里云ECS设置Java开发环境
然后启动arthas-boot.jar:
java -jar arthas-boot.jar
如下图所示:
提示信息是说Arthas没有检测到Java进程,建议输入一个pid,因为环境中确实还没有Java进程,因此这个提示正常,到此Arthas在Linux上安装成功。
2.2 卸载
在Linux平台中,卸载Arthas直接删除下面的2个文件即可:
rm -rf ~/.arthas/rm -rf ~/logs/arthas
Linux中查看隐藏目录的命令:
ll- a
3、快速入门
(1)启动math-game
Arthas自带了一个Java进程的demo,目的是方便大家上手练习的时候有个Java进程操作,这个math-game.jar跟arthas-boot.jar在一个目录下,如下图所示:
math-game是一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果,源码如下:
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 static void main(String[] args) throws InterruptedException {MathGame game = new MathGame();while (true) {game.run();TimeUnit.SECONDS.sleep(1);}}public void run() throws InterruptedException {try {int number = random.nextInt()/10000;List<Integer> primeFactors = primeFactors(number);print(number, primeFactors);} catch (Exception e) {System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());}}public static void print(int number, List<Integer> primeFactors) {StringBuffer sb = new StringBuffer(number + "=");for (int factor : primeFactors) {sb.append(factor).append('*');}if (sb.charAt(sb.length() - 1) == '*') {sb.deleteCharAt(sb.length() - 1);}System.out.println(sb);}public List<Integer> primeFactors(int number) {if (number < 2) {illegalArgumentCount++;throw new IllegalArgumentException("number is: " + number + ", need >= 2");}List<Integer> result = new ArrayList<Integer>();int i = 2;while (i <= number) {if (number % i == 0) {result.add(i);number = number / i;i = 2;} else {i++;}}return result;}}
启动这个jar:
java -jar math-game.jar
屏幕中打印随机数的值以及该值的质因数分解结果,如下图所示:
(2)启动arthas
再打开一个Linux窗口,启动arthas:
java -jar arthas-boot.jar
选择math-game.jar这个进程的下标,回车/enter。Arthas会**attach**到目标进程上,并输出日志:
注意:
- Arthas attach到目标进程,意思是Arthas诊断工具已经与目标进程建立联系,可以通过Arthas对目标进程进行监控诊断;
- Arthas 成功attach到目标进程,左下角会显示arthas@目标进程号。
(3)查看dashboard
输入dashboard,按回车/enter,会展示当前进程的信息,按ctrl+c可以中断执行,如下图所示:
dashboard中文也是仪表盘的意思,即目标进程的信息在这个仪表盘中显示,且不断刷新中,有关dashboard命令在5.1小节中再详细介绍。
(4)通过thread命令来获取到math-game进程的Main Class
thread1会打印线程ID 1的栈,通常是main函数的线程,如下图所示:
有关thread命令在5.2小节中再详细介绍。
(5)通过jad来反编译Main Class
通过该命令可以基于class文件获得对应的java源码,如下图所示:
(6)watch
看demo.MathGame类的源码可以知道里面有个primeFactors函数,可以通过watch命令来查看demo.MathGame#primeFactors函数的返回值,如下图所示:
有关watch命令在7.2小节中再详细介绍。
(7)退出Arthas
# 退出Arthas客户端,Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时可以直接连接上。
quit
exit
# 退出Arthas服务端
stop
4、基本命令
详细的基本命令见官网:https://arthas.aliyun.com/doc/advanced-use.html#id2
基本命令汇总至表格:
| 命令 | 说明 |
|---|---|
| help | 查看命令帮助信息 |
| cat | 打印文件内容,和linux里的cat命令类似 |
| echo | 打印参数,和linux里的echo命令类似 |
| grep | 匹配查找,和linux里的grep命令类似 |
| base64 | base64编码转换,和linux里的base64命令类似 |
| tee | 复制标准输入到标准输出和指定的文件,和linux里的tee命令类似 |
| pwd | 返回当前的工作目录,和linux命令类似 |
| cls | 清空当前屏幕区域 |
| session | 查看当前会话的信息 |
| reset | 重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类 |
| version | 输出当前目标 Java 进程所加载的 Arthas 版本号 |
| history | 打印命令历史 |
| quit | 退出当前 Arthas 客户端,其他 Arthas 客户端不受影响 |
| stop | 关闭 Arthas 服务端,所有 Arthas 客户端全部退出 |
| keymap | Arthas快捷键列表及自定义快捷键 |
4.1 help
help命令可以展示Arthas中的所有命令及对应的说明,方便我们忘记了命令时的快速查询,如下图所示:
4.2 cat
4.3 grep
和linux里的grep命令类似。
举例:sysprop命令是Arthas中查看当前JVM的系统属性,由于该命令会打印很多信息到屏幕上,而有时我们只关注特定信息,因此需要grep命令过滤,比如:
sysprop | grep java
sysprop | grep java -n
sysenv | grep -v JAVA
sysenv | grep -e "(?i)(JAVA|sun)" -m 3 -C 2
sysenv | grep JAVA -A2 -B3
thread | grep -m 10 -e "TIMED_WAITING|WAITING"
说明:
-n:显示行号;
-m:设定最大展示条数;
-A:显示该行之后多少条;
-B:显示该行之前多少条;
-v:显示不匹配的行记录;
-e:使用正则表达式匹配。
4.4 pwd
4.5 cls
4.6 session
查看当前会话的信息,显示当前绑定的pid以及会话id。
说明:
- JAVA_PID:当前Arthas attach的Java进程Id;
- SESSION_ID:会话ID。
4.7 reset
重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端stop时会重置所有增强过的类。在执行Arthas的watch/trace 等命令时,实际上是修改了应用的字节码,插入增强的代码。显式执行 reset 命令,可以清除掉这些增强代码。 ```bash还原指定类
reset TestClass
还原所有以List结尾的类
reset *List
还原所有类
reset
结果中会展示reset的类的数目和方法的数目,如下:<br />
<a name="B9qXt"></a>
## 4.8 version
输出当前目标 Java 进程所加载的 Arthas 版本号。
<a name="Zlgax"></a>
## 4.9 quit
退出当前 Arthas 客户端,其他 Arthas 客户端不受影响。等同于exit、logout、q三个指令。只是退出当前Arthas客户端,Arthas的服务器端并没有关闭,所做的修改也不会被重置。
<a name="pnnuE"></a>
## 4.10 stop
关闭 Arthas 服务端,所有 Arthas 客户端全部退出。关闭Arthas服务器之前,会重置掉所有做过的增强类。但是用redefine重加载的类内容不会被重置。
<a name="JTJhO"></a>
## 4.11 keymap
keymap命令输出当前的快捷键映射表。
<a name="Omg0G"></a>
## 4.12 history
打印命令历史。
```bash
# 查看所有历史命令
history
# 查看最近执行的3条命令
history 3
# 清空历史指令
history -c
5、系统命令
5.1 dashboard
展示当前系统的实时数据面板,打印到控制台中,并不断刷新,ctrl + c退出。
分区如下:
对Thread一栏的列名做个说明:
| 列名 | 说明 |
|---|---|
| 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线程 |
复习一下线程的几种状态:
| 线程状态 | 说明 |
|---|---|
| new | 用new操作符创建一个线程:new Thread(r),需要给线程分配资源(比如内存)后才能变为runnable状态 |
| runnable | 一旦调用了start方线程会进入runnable(就绪)状态,此时线程并没有运行,而是等待CPU临幸自己,当线程进入了CPU给它的时间片后,才真正进入了运行(running)状态 |
| running | 线程的运行状态,此时线程进CPU时间片后,开始疯狂输出(做任务) |
| waiting | 当线程等待另一个线程通知调度器一个条件时,该线程进入等待状态(waiting),当线程得到调度器通知可以被唤醒时,线程会重新进入runnable状态 |
| time waiting | 计时等待,比waiting多了个条件,当休眠的时间超过设置的时,线程也会被唤醒,重新进入runnable状态 |
| blocked | 阻塞状态。当线程试图访问一份资源,获得对象锁时,此时对象锁被其他线程所持有,线程只能进入blocked状态,只有当其他线程释放对象锁,并且线程调度器允许它持有该锁时,才会由blocked状态进入runnable状态 |
| dead | 死亡状态 |
5.2 thread
查看当前线程信息,查看线程的堆栈。
(1)展示当前进程中的所有线程信息
thread

列名和dashboard命令的Thread区域中的列名一致。
(2)展示当前最忙的n个线程并打印堆栈
thread -n 线程数

(3)显示指定线程的运行堆栈
thread 线程id

(4)显示当前阻塞其他线程的线程
有时候我们发现应用卡住了, 通常是由于某个线程拿住了某个锁, 并且其他线程都在等待这把锁造成的。 为了排查这类问题, arthas提供了thread-b, 一键找出那个罪魁祸首。这个命令在检测死锁中经常用到。
thread -b
当前无阻塞线程如下:
当前有阻塞线程如下:
上图中id为9的名称为Jack的线程被id为10的名称为Rose的线程阻塞。
注意: 目前只支持找出synchronized关键字阻塞住的线程, 如果是java.util.concurrent.Lock, 目前还不支持。
(5)打印指定线程状态的线程
thread --state 线程状态
// eg
thread --state BLOCKED
thread --state RUNNABLE
打印结果:
[arthas@22366]$ thread --state BLOCKED
Threads Total: 29, NEW: 0, RUNNABLE: 7, BLOCKED: 1, WAITING: 11, TIMED_WAITING: 10, TERMINATED: 0
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
29 Thread-7 main 5 BLOCKED 0 0:0 false false
Affect(row-cnt:0) cost in 102 ms.
5.3 jvm
5.4 sysprop
查看当前JVM的系统属性(SystemProperty),以k-v的形式展示。
# 查看单个属性
sysprop KEY
# 修改单个属性
sysprop KEY VALUE
5.5 sysenv
查看当前JVM的环境变量,以k-v的形式展示。
# 查看所有环境变量
sysenv
# 查看单个环境变量
sysenv KEY
5.6 vmoption
查看,更新VM诊断相关的参数,以k-v的形式展示。
# 查看单个参数
vmoption KEY
# 修改单个参数
vmoption KEY VALUE
5.7 getstatic
通过getstatic命令可以方便的查看类的静态属性,使用方法为getstatic class_name field_name,推荐使用ognl命令更灵活。
5.8 ognl
6、类命令
6.1 sc
“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) |
(1)模糊搜索demo包下的所有类
sc demo.*

(2)打印类的详细信息
sc -d demo.MathGame

(3)打印类的属性
sc -df demo.MathGame

说明:
- 多个属性,中间会有一个换行符分隔开;
- 属性以k-v的形式展示。
6.2 sm
“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。
参数说明:
| 参数名称 | 参数说明 |
|---|---|
| class-pattern | 类名表达式匹配 |
| method-pattern | 方法名表达式匹配 |
| [d] | 展示每个方法的详细信息 |
| [E] | 开启正则表达式匹配,默认为通配符匹配 |
| [c:] | 指定class的 ClassLoader 的 hashcode |
| [classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
| [n:] | 具有详细信息的匹配类的最大数量(默认为100) |
(1)查看指定类中的方法
sm 类名

(2)查看指定类的指定方法的详细信息
sm -d 类名 方法名
6.3 jad
反编译指定已加载类的源码,jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;
- 在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便;
- 当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解。
```bash
反编译时仅显示源码
jad —source-only 类名
反编译指定函数
jad 类名 方法名
反编译时不显示行号
jad 类名 —lineNumber false
通常使用jad命令会把信息打印在控制台上,我们开源码时肯定想vim看,因此一般会把jad反编译出的源码写到一个文本文件中,如下:
```bash
jad demo.MathGame primeFactors --source-only --lineNumber false > primeFactors.java
这时在arthas-boot.jar的目录下就会生成一个文件名为primeFactors.java的文件。
6.4 mc
Memory Compiler/内存编译器,编译.java文件生成.class。
# 编译指定源代码
mc java文件名
# 通过-c指定classloader
mc -c java文件名
# 通过-d命令指定输出目录,同时编译多个源文件
mc -d class文件输出目录 源文件1 源文件2
6.5 redefine
加载外部的.class文件,redefine jvm已加载的类。推荐使用 retransform 命令。
6.6 retransform
6.6.1 retransform使用
加载外部的.class文件,retransform jvm已加载的类。
# retransform 指定的 .class 文件
retransform class文件
加载指定的 .class 文件,然后解析出class name,再retransform jvm中已加载的对应的类。每加载一个 .class 文件,则会记录一个 retransform entry。如果多次执行 retransform 加载同一个 class 文件,则会有多条 retransform entry。
# 查看 retransform entry
retransform -l
# 删除指定 retransform entry
retransform -d id
# 删除所有 retransform entry
retransform --deleteAll
# 显式触发 retransform
retransform --classPattern 类名
如果对某个类执行 retransform 之后,想消除影响,则需要:
- 删除这个类对应的 retransform entry;
- 重新触发 retransform。
如果不清除掉所有的 retransform entry,并重新触发 retransform ,则arthas stop时,retransform过的类仍然生效。
6.6.2 retransform的限制
- 不允许新增加field/method;
正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效:
public class MathGame { public static void main(String[] args) throws InterruptedException { MathGame game = new MathGame(); while (true) { game.run(); TimeUnit.SECONDS.sleep(1); // 这个不生效,因为代码一直跑在 while里 System.out.println("in loop"); } } public void run() throws InterruptedException { // 这个生效,因为run()函数每次都可以完整结束 System.out.println("call run()"); try { int number = random.nextInt(); List<Integer> primeFactors = primeFactors(number); print(number, primeFactors); } catch (Exception e) { System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage()); } }6.6.3 结合 jad/mc 命令使用
这就是线上实际的问题,有时候是代码的问题,如果修改代码重新出补丁包,流程太繁琐,可能时间上来不及,通过Aarthas这一套热加载class文件可能可以快速解决问题。但是仍然不推荐这么做,因为有可能会出现修改引入,而且未经过严格的测试就部署到生产环境或者客户的环境是很有风险的,因此实际线上环境并不推荐这么做。
这里知道有这么个解决方案就可以了,不要使用:jad命令反编译,然后可以用其它编译器,比如vim来修改源码;
- mc命令来内存编译修改过的代码;
- 用retransform命令加载新的字节码。 ```bash jad —source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
mc /tmp/UserController.java -d /tmp
retransform /tmp/com/example/demo/arthas/user/UserController.class
<a name="vftNZ"></a>
## 6.7 dump
将已加载的字节码文件保存到指定的目录,默认保存的目录是:/logs/arthas/classdump/。<br />使用举例:
```bash
# 把String类的字节码文件保存到默认的目录:/logs/arthas/classdump
dump java.lang.String
# 把demo包下的所有类的字节码文件保存到默认的目录:/logs/arthas/classdump
dump demo.*
# 把MathGame类的字节码文件保 -ump存到指定的目录:/tmp/output
dump -d /tmp/output demo.MathGame
6.8 classloader
获取类加载器的信息,主要作用有两个:
- 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去加载指定的类 |
(1)classloader
(2)classloader -l
(3)classloader -a
列出类加载器加载的所有类,谨慎使用。
(4)classloader -c
查看指定hashcode的类加载器所在的jar包。
(5)classloader —load
使用指定的类加载器加载指定的类。
classloader -c 指定的类加载器的hashcode --loader 类名(全路径)
# 举例
classloader -c 3d4eac69 --load demo.MathGame
7、增强命令
这一节介绍的增强命令在实际定位过程中经常用到,且大多数命令我们并不需要记忆命令拼接方法或者网上搜索如何拼接命令,只需要在IDEA里下载一个“arthas idea” 的插件,该插件会根据我们选择的方法自动拼接命令,我们只需粘贴到arthas的控制台上即可。
7.1 monitor
monitor命令用来监视一个时间段内指定类的指定方法的执行次数、成功失败次数、耗时时间等信息。
- 对匹配 class-pattern/method-pattern/condition-express的类、方法的调用进行监控;
- monitor 命令是一个非实时返回命令;
- 实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。
参数说明:
| 参数名称 | 参数说明 |
|---|---|
| class-pattern | 类名表达式匹配 |
| method-pattern | 方法名表达式匹配 |
| condition-express | 条件表达式 |
| [E] | 开启正则表达式匹配,默认为通配符匹配 |
| [c:] | 统计周期,默认值为120秒 |
| [b] | 在方法调用之前计算condition-express |
对于-c这个参数特别说明一下:由于monitor命令不是实时返回的,如果不显式指定-c参数的统计周期,默认为120s,这个值有时候会显得很长,因此在快速排查问题时,需要用-c参数指定返回周期。
# 常用命令形式
monitor 类名(全路径)方法名 -c 统计周期
监控的维度说明:
| 监控项 | 说明 |
|---|---|
| timestamp | 时间戳 |
| class | Java类名称 |
| method | 方法(构造方法、普通方法) |
| total | 调用次数 |
| success | 成功次数 |
| fail | 失败次数 |
| rt | 平均调用时间 |
| fail-rate | 失败率 |
举例:
monitor demo.MathGame primeFactors -c 5
7.2 watch
watch命令用来观察指定方法的调用情况,方法监控的维度有入参、返回值、抛出异常,通过编写OGNL表达式进行变量的查看,也可以通过IDEA的“arthas idea”插件来快速拼写OGNL表达式。
watch命令在实际排查定位问题中是使用最多的。
特别说明:
- watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后;
- 4个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出;
- 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参;
- 当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在;
- 在watch命令的结果里,会打印出location信息。location有三种可能值:AtEnter,AtExit,AtExceptionExit。对应函数入口,函数正常return,函数抛出异常。
watch命令的参数说明:
| 参数名称 | 参数说明 |
|---|---|
| class-pattern | 类名表达式匹配 |
| method-pattern | 方法名表达式匹配 |
| express | 观察表达式,仅限OGNL表达式,默认值:{params, target, returnObj} |
| condition-express | 条件表达式 |
| [b] | 在方法调用之前观察 |
| [e] | 在方法异常之后观察 |
| [s] | 在方法返回之后观察 |
| [f] | 在方法结束之后(正常返回和异常返回)观察 |
| [E] | 开启正则表达式匹配,默认为通配符匹配 |
| [x:] | 指定输出结果的属性遍历深度,默认为 1 |
7.2.1 最简单的使用方法
观察表达式缺省时,默认值是{params,target,returnObj}
watch demo.MathGame primeFactors -x 2

说明:
- params是方法出参,由于一个方法可能有多个参数,因此上图中的橙色部分是一个数组,如果仅监视指定的出参可以使用params[i],i代表第几个出参;
- 返回值具体情况可能不同,如上图中方法正常结束调用返回一个ArrayList,对应的location为AtExit;方法异常调用返回null,对应的location为AtExceptionExit;
- target代表调用方法对应的对象实例,如上图中的蓝色部分,打印了MathGame类的两个属性值:random和illegalArgumentCount;
每个方法都会打印方法的执行时间,在上图中是cost对应的数值,忘用颜色框标注了。
7.2.2 观察方法执行前的入参
watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b
说明:由于加了-b参数,代表方法没有真正执行,因此方法的返回结果是null。
7.2.3 同时观察方法调用前和方法返回后
watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2
说明:参数里-n 2,表示只执行两次;
- 这里输出结果中,第一次输出的是方法调用前的观察表达式的结果,可以看到返回值为null;第二次输出的是方法返回后的表达式的结果,这里由于是异常返回null,不异常的话这里应该返回一个质因数的分解结果的List;
结果的输出顺序和事件发生的先后顺序一致,和命令中 -s-b 的顺序无关。
7.2.4 观察当前对象中的某个属性
watch demo.MathGame primeFactors 'target.illegalArgumentCount'
说明:OGNL表达式中,target代表的是对象实例,观察类的哪个属性,就在target后面用
.属性名称表示即可。7.2.5 观察异常信息
# 观察当抛出异常时,MathGame类的primeFactors方法的第一个入参params[0]及异常的信息 watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2
说明:-e表示抛出异常时才触发;
-
7.2.6 条件表达式
# 观察当第一个出参值小于0时的方法调用情况 watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
说明: -
7.2.7 按照耗时进行过滤
# 只有当耗时大于0.04ms时才会输出,过滤掉执行时间小于0.04ms的调用 watch demo.MathGame primeFactors '{params, returnObj}' '#cost>0.04' -x 2
说明: cost代表方法调用时间,单位是ms。
7.3 trace
trace命令在平时的排查定位问题中也使用较多,尤其是做性能分析、压测的时候。
trace命令能打印方法内部调用路径,并输出方法路径上的每个内部调用方法的节点上耗时。
参数说明:
| 参数名称 | 参数说明 |
|---|---|
| class-pattern | 类名表达式匹配 |
| method-pattern | 方法名表达式匹配 |
| condition-express | 条件表达式 |
| [E] | 开启正则表达式匹配,默认为通配符匹配 |
| [n:] | 命令执行次数 |
| #cost | 方法执行耗时 |
7.3.1 查看指定类的指定方法
# trace 类名 方法名
trace demo.MathGame run
7.3.2 指定显示多少条
trace demo.MathGame run -n 3

说明:
- -n参数指定显示多少条。
7.3.3 内部方法中包含jdk函数
平时定位问题时肯定不care jdk函数的调用时间,因为是标准接口嘛,但有时确实怀疑jdk方法效率问题,或者研究源码时可以开启这个参数,否则平时几乎不用。
trace --skipJDKMethod false demo.MathGame run

说明:
默认情况下skipJDKMethod参数值为true,即默认不打印jdk接口调用情况;当skipJDKMethod参数显式声明为false时,才打印jdk接口调用情况。
7.3.4 根据接口执行时间过滤
# 条件表达式condition-express的应用,条件表达式也是OGNL表达式 trace demo.MathGame run '#cost > 0.5'
说明:cost代表方法执行耗时,注意该耗时是对应前面声明的方法名的,单位是ms。
7.4 stack
stack命令输出当前方法被调用的路径。很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
参数说明:
| 参数名称 | 参数说明 |
|---|---|
| class-pattern | 类名表达式匹配 |
| method-pattern | 方法名表达式匹配 |
| condition-express | 条件表达式 |
| [E] | 开启正则表达式匹配,默认为通配符匹配 |
| [n:] | 执行次数限制 |
7.4.1 最简单的使用
stack demo.MathGame primeFactors -n 2

说明:
- 方法打印情况,最上面打印的是最底层的方法,从上面到下面依次是上游调用的链路,最下面打印的方法是最顶层调用的方法;
-
7.4.2 根据表达式过滤
# 打印第一个出参值小于0的方法调用 stack demo.MathGame primeFactors 'params[0]<0' -n 27.4.3 根据执行时间过滤
# 打印primeFactors方法执行时间大于0.05s的方法调用路径 stack demo.MathGame primeFactors '#cost>0.05'
说明: 同trace、watch命令一样,#cost在OGNL表达式中代表方法耗时。
7.5 tt
tt命令记录了方法执行数据的时空隧道,具体指记录下指定方法每次调用的入参和返回信息,并能随时查阅并复原历史调用情况。
有时候复现一次问题场景可能很麻烦,需要上游服务准备数据或者进行接口调用,因此为了不每次都麻烦上游服务触发问题场景,我们可以用tt命令将历史调用情况记录下来。
参数说明:
| 参数名称 | 参数说明 |
|---|---|
| -t | 记录下指定类的指定方法的每次调用情况 |
| -n | 指定需要记录的次数 |
| -l | 查看历史记录列表 |
| -s | 带条件搜索历史记录(search) |
| -i | 查看某一条历史记录详情 |
| -p | 重新调用指定的索引号对应的历史记录 |
7.5.1 基本使用
tt -t demo.MathGame primeFactors

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

tt -s的其他例子:
# 根据入参过滤
tt -s 'params[2].getRecordId() == 110213603'
# 根据返回结果过滤
tt -s 'returnObj.isSuccess() == false'
# 根据入参和返回结果过滤
tt -s 'returnObj.isSuccess() == true && params[2].getRecordId() == 110213603'
7.5.4 查询指定索引号的记录详情
tt -i 索引号

列名解释:
| 表格字段 | 字段解释 |
|---|---|
| INDEX | 时间片段记录编号,每一个编号代表着一次调用,后续tt还有很多命令都是基于此编号指定记录操作,非常重要。 |
| GMT-CREATE | 方法执行的时刻 |
| COST(ms) | 方法执行的耗时 |
| OBJECT | 执行对象的hashCode(),注意,曾经有人误认为是对象在JVM中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体 |
| CLASS | 执行的类名 |
| METHOD | 执行的方法名 |
| IS-RETURN | 方法是否以正常返回的形式结束 |
| IS-EXCEPTION | 方法是否以抛异常的形式结束 |
| PARAMETERS[0] | 方法入参 |
| THROW-EXCEPTION | 抛出的异常堆栈信息 |
7.5.5 重新触发指定index的调用
# tt -i index号 -p
tt -i 1000 -p
# tt -i index号 -p --replay-times 重复调用次数
tt-i 1000 -p --replay-times 3
# tt -i index号 -p --replay-times 重复调用次数 --replay-interval 调用间隔(ms)
tt-i 1000 -p --replay-times 3 --replay-interval 2000
7.6 profiler生成火焰图
7.6.1 什么是火焰图
火焰图(Flame Graph)是由 Linux 性能优化大师 Brendan Gregg 发明的,和所有其他的 profiling 方法不同的是,火焰图以一个全局的视野来看待时间分布,它从底部往顶部,列出所有可能导致性能瓶颈的调用栈。
火焰图有以下特征(这里以 on-cpu 火焰图为例):
- 每一列代表一个调用栈,每一个格子代表一个函数
- 纵轴展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 cpu 的函数。
- 横轴的意义是指:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。需要注意的是它并不代表时间。
- 横轴格子的宽度代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。
- 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。
- 其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。
7.6.2 profiler命令
```bash启动profiler进行采样
profiler start
获取已采集的sample的数量
profiler getSamples
查看profiler状态
profiler status
停止profiler,在指定路径下生成火焰图
profiler stop
查看profiler支持的events
8、插件“arthas idea” 和 “ArthasHotSwap”
8.1 arthas idea
这是Idea的一个插件,安装后可以点击鼠标右键就能将指定方法或者指定类的相关命令拷贝到粘贴板上,不需要再记那些命令和自己拼OGNL表达式了,如下图:
8.2 ArthasHotSwap
使用见参考博客:如何使用Arthas提高日常开发效率?
9、综合案例
可以参考这篇博客:如何使用Arthas提高日常开发效率?
参考
Arthas 用户文档
【Java线上诊断神器Arthas】——阿里巴巴开源的诊断工具Arthas,轻松搞定线上疑难问题!
阿里云ECS设置Java开发环境
程序员精进之路:性能调优利器—火焰图
如何使用Arthas提高日常开发效率?
