1、简介
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。开发人员可以使用 Arthas 在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。
1.1、Arthas 能为你做什么?
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从 JVM 内查找某个类的实例?
Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
2、Arthas 安装
Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,建议启动 Arthas 时,和目标 Java 应用使用相同版本的 JDK。
Arthas 支持以下的一些安装方式,用户可以根据环境,选择合适的安装方式。
本地启动 Arthas 时,如果当前主机没有 Java 进程,Arthas 会直接退出,如果有 Java 进程,会提示选择需要 attach 的 Java 进程序号。
2.1、快速安装
快速安装的 Arthas,在执行命令时可能会需求联网下载资源,推荐在本地试用时使用。
2.1.1、使用 arthas-boot(试用推荐)
下载“arthas-boot.jar”,然后用 java -jar
的方式启动:
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
打印帮助信息:
java -jar arthas-boot.jar -h
可以手动下载,如果网络比较慢,可以使用 aliyun 的镜像: java -jar arthas-boot.jar --repo-mirror aliyun --use-http
2.1.2、使用 as.sh
Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行执行即可:
curl -L https://arthas.aliyun.com/install.sh | sh
上述命令会下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。
直接在 shell 下面执行./as.sh,就会进入交互界面。
也可以执行./as.sh -h来获取更多参数信息。
2.2、全量安装
最新版本,点击下载:https://arthas.aliyun.com/download/latest_version?mirror=aliyun
解压后,在文件夹里有“arthas-boot.jar”,直接用java -jar
的方式启动:
java -jar arthas-boot.jar
打印帮助信息:
java -jar arthas-boot.jar -h
2.2.1、通过 deb 来安装
在 releases 页面下载 deb 包: https://github.com/alibaba/arthas/releases
2.2.1.1、安装 deb
sudo dpkg -i arthas*.deb
2.2.1.2、deb 安装的用法
as.sh
2.3、手动安装
手动安装是使用已下载的压缩包执行本地安装的方法。推荐在服务器等联网受限的环境使用。
1.下载最新版本
最新版本,点击下载:https://arthas.aliyun.com/download/latest_version?mirror=aliyun
2.解压缩 arthas 的压缩包
unzip arthas-packaging-bin.zip
3.安装 Arthas
安装之前最好把所有老版本的 Arthas 全都删掉
sudo su javaer
rm -rf ~/.arthas/lib/*
cd arthas
./install-local.sh
注意,这里根据你需要诊断的 Java 进程的所属用户进行切换。
4.启动 Arthas
启动之前,请确保老版本的 Arthas 已经 stop。
./as.sh
2.4、卸载
在 Linux/Unix/Mac 平台,删除下面文件:
rm -rf ~/.arthas/
rm -rf ~/logs/arthas
Windows 平台直接删除 ”user home“ 下面的 .arthas
和logs/arthas
目录
3、快速入门
3.1、启动 math-game
math-game 是一个官方提供的一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。
curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar
math-game 也存在于下载安装包中,如果使用手动安装,可以直接执行,无需下载。
3.2、启动 arthas
java -jar arthas-boot.jar
- 执行该程序的用户需要和目标进程具有相同的权限。比如以 javaer 用户来执行:
sudo su javaer && java -jar arthas-boot.jar
或sudo -u javaer -EH java -jar arthas-boot.jar
。 - 如果 attach 不上目标进程,可以查看“~/logs/arthas/”目录下的日志。
java -jar arthas-boot.jar -h
打印更多参数信息。
输入序号,选择应用 java 进程:
Arthas script version: 3.7.2
[INFO] JAVA_HOME: /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home
Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 37928 math-game.jar
math-game 进程是第 1 个,则输入 1,再输入 回车/enter
。Arthas 会 attach 到目标进程上,并输出日志:
Arthas home: /arthas-packaging-3.7.2-bin
Calculating attach execution time...
Attaching to 37928 using version /arthas-packaging-3.7.2-bin...
real 0m1.166s
user 0m0.434s
sys 0m0.049s
Attach success.
telnet connecting to arthas server... current timestamp is 1713150720
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 3.7.2
main_class demo.MathGame
pid 37928
time 2024-04-15 11:12:00
[arthas@37928]$
3.3、查看 dashboard
输入 dashboard
,按回车/enter
,会展示当前进程的信息,按ctrl+c
可以中断执行。
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTED DAEMON
-1 C2 CompilerThread0 - -1 - 0.63 0.031 0:4.439 false true
-1 C1 CompilerThread0 - -1 - 0.35 0.017 0:1.445 false true
94 Timer-for-arthas-dashboard-4ba5e31d-19db-48d6- system 5 RUNNABLE 0.33 0.016 0:0.018 false true
92 arthas-NettyHttpTelnetBootstrap-3-2 system 5 RUNNABLE 0.13 0.006 0:0.072 false true
-1 VM Periodic Task Thread - -1 - 0.07 0.003 0:2.219 false true
-1 G1 Young RemSet Sampling - -1 - 0.03 0.001 0:0.749 false true
1 main main 5 TIMED_WAITING 0.02 0.001 0:0.879 false false
-1 VM Thread - -1 - 0.01 0.000 0:0.138 false true
85 Keep-Alive-Timer InnocuousThreadGroup 8 TIMED_WAITING 0.0 0.000 0:0.000 false true
-1 GC Thread#1 - -1 - 0.0 0.000 0:0.045 false true
-1 GC Thread#2 - -1 - 0.0 0.000 0:0.045 false true
-1 GC Thread#3 - -1 - 0.0 0.000 0:0.046 false true
-1 GC Thread#0 - -1 - 0.0 0.000 0:0.046 false true
2 Reference Handler system 10 RUNNABLE 0.0 0.000 0:0.000 false true
3 Finalizer system 8 WAITING 0.0 0.000 0:0.000 false true
4 Signal Dispatcher system 9 RUNNABLE 0.0 0.000 0:0.000 false true
11 Attach Listener system 9 RUNNABLE 0.0 0.000 0:0.042 false true
83 arthas-timer system 9 WAITING 0.0 0.000 0:0.000 false true
86 arthas-NettyHttpTelnetBootstrap-3-1 system 5 RUNNABLE 0.0 0.000 0:0.015 false true
Memory used total max usage GC
heap 90M 256M 4096M 2.20% gc.g1_young_generation.count 6
g1_eden_space 48M 111M -1 43.24% gc.g1_young_generation.time(ms) 39
g1_old_gen 31M 134M 4096M 0.76% gc.g1_old_generation.count 0
g1_survivor_space 11M 11M -1 100.00% gc.g1_old_generation.time(ms) 0
nonheap 57M 60M -1 94.18%
codeheap_'non-nmethods' 1M 2M 5M 21.62%
metaspace 42M 43M -1 96.86%
codeheap_'profiled_nmethods' 7M 7M 117M 6.14%
compressed_class_space 4M 5M 1024M 0.48%
codeheap_'non-profiled_nmethods' 1M 2M 117M 1.60%
mapped 0K 0K - 0.00%
direct 12M 12M - 100.00%
Runtime
os.name Mac OS X
os.version 10.15.7
java.version 11.0.18
java.home /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home
systemload.average 2.39
processors 4
# timestamp/uptime Mon Apr 15 13:15:24 CST 2024/3462s
3.4、通过 thread 命令来获取到math-game进程的 Main Class
thread 1
会打印线程 ID 1 的栈,通常是 main 函数的线程。
$ thread 1
"main" Id=1 TIMED_WAITING
at java.base@11.0.18/java.lang.Thread.sleep(Native Method)
at java.base@11.0.18/java.lang.Thread.sleep(Thread.java:334)
at java.base@11.0.18/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at app//demo.MathGame.main(MathGame.java:17)
3.5、通过 jad 来反编译 Main Class
$ jad demo.MathGame
ClassLoader:
+-jdk.internal.loader.ClassLoaders$AppClassLoader@277050dc
+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@bcdf432
Location:
/arthas-packaging-3.7.2-bin/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 746 ms.
3.6、watch
通过watch
命令来查看demo.MathGame#primeFactors
函数的返回值:
$ watch demo.MathGame primeFactors returnObj
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 148 ms, listenerId: 1
method=demo.MathGame.primeFactors location=AtExit
ts=2024-04-15 13:23:13; [cost=3.269941ms] result=@ArrayList[
@Integer[7],
@Integer[17789],
]
method=demo.MathGame.primeFactors location=AtExit
ts=2024-04-15 13:23:14; [cost=0.057208ms] result=@ArrayList[
@Integer[3],
@Integer[3],
@Integer[5],
@Integer[5],
@Integer[11],
]
method=demo.MathGame.primeFactors location=AtExit
ts=2024-04-15 13:23:15; [cost=1.968912ms] result=@ArrayList[
@Integer[3],
@Integer[66071],
]
3.7、退出 arthas
如果只是退出当前的连接,可以用quit
或者exit
命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出 arthas,可以执行stop
命令。
4、表达式核心变量
Arthas 的命令参数中经常会出现“匹配表达式”、“观察表达式” 概念, 无论是匹配表达式也好、观察表达式也罢,他们核心判断变量都是围绕着一个 Arthas 中的通用通知对象 Advice 进行。
它的简略代码结构如下:
public class Advice {
private final ClassLoader loader;
private final Class<?> clazz;
private final ArthasMethod method;
private final Object target;
private final Object[] params;
private final Object returnObj;
private final Throwable throwExp;
private final boolean isBefore;
private final boolean isThrow;
private final boolean isReturn;
// getter/setter
}
这里列一个表格来说明不同变量的含义:
变量名 | 变量解释 |
---|---|
loader | 本次调用类所在的 ClassLoader |
clazz | 本次调用类的 Class 引用 |
method | 本次调用方法反射引用 |
target | 本次调用类的实例 |
params | 本次调用参数列表,这是一个数组,如果方法是无参方法则为空数组 |
returnObj | 本次调用返回的对象。当且仅当 isReturn==true 成立时候有效,表明方法调用是以正常返回的方式结束。如果当前方法无返回值 void ,则值为 “null” |
throwExp | 本次调用抛出的异常。当且仅当 isThrow==true 成立时有效,表明方法调用是以抛出异常的方式结束。 |
isBefore | 辅助判断标记,当前的通知节点有可能是在方法一开始就通知,此时 isBefore==true 成立,同时 isThrow==false 和 isReturn==false ,因为在方法刚开始时,还无法确定方法调用将会如何结束。 |
isThrow | 辅助判断标记,当前的方法调用以抛异常的形式结束。 |
isReturn | 辅助判断标记,当前的方法调用以正常返回的形式结束。 |
所有变量都可以在表达式中直接使用,如果在表达式中编写了不符合 OGNL 脚本语法或者引入了不在表格中的变量,则退出命令的执行;用户可以根据当前的异常信息修正条件表达式或观察表达式
- 特殊用法请参考:https://github.com/alibaba/arthas/issues/71
- OGNL 表达式官网:https://commons.apache.org/dormant/commons-ognl/language-guide.html
5、命令列表
5.1、基础命令
- base64:base64 编码转换,和 linux 里的 base64 命令类似
- cat:打印文件内容,和 linux 里的 cat 命令类似
- cls:清空当前屏幕区域
- echo:打印参数,和 linux 里的 echo 命令类似
- grep:匹配查找,和 linux 里的 grep 命令类似
- help:查看命令帮助信息
- history:打印命令历史
- keymap:Arthas 快捷键列表及自定义快捷键
- pwd:返回当前的工作目录,和 linux 命令类似
- quit:退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
- reset:重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
- session:查看当前会话的信息
- stop:关闭 Arthas 服务端,所有 Arthas 客户端全部退出
- tee:复制标准输入到标准输出和指定的文件,和 linux 里的 tee 命令类似
- version:输出当前目标 Java 进程所加载的 Arthas 版本号
5.2、JVM 相关
- dashboard:当前系统的实时数据面板
- getstatic:查看类的静态属性
- heapdump:dump java heap, 类似 jmap 命令的 heap dump 功能
- jvm:查看当前 JVM 的信息
- logger:查看和修改 logger
- mbean:查看 Mbean 的信息
- memory:查看 JVM 的内存信息
- ognl:执行 ognl 表达式
- perfcounter:查看当前 JVM 的 Perf Counter 信息
- sysenv:查看 JVM 的环境变量
- sysprop:查看和修改 JVM 的系统属性
- thread:查看当前 JVM 的线程堆栈信息
- vmoption:查看和修改 JVM 里诊断相关的 option
- vmtool:从 jvm 里查询对象,执行 forceGc
5.3、class/classloader 相关
- classloader:查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource
- dump:dump 已加载类的 byte code 到特定目录
- jad:反编译指定已加载类的源码
- mc:内存编译器,内存编译.java文件为.class文件
- redefine:加载外部的.class文件,redefine 到 JVM 里
- retransform:加载外部的.class文件,retransform 到 JVM 里
- sc:查看 JVM 已加载的类信息
- sm:查看已加载类的方法信息
5.4、monitor/watch/trace 相关
请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行
stop
或将增强过的类执行reset
命令。
- monitor:方法执行监控
- stack:输出当前方法被调用的调用路径
- trace:方法内部调用路径,并输出方法路径上的每个节点上耗时
- tt:方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
- watch:方法执行数据观测
5.5、profiler/火焰图
- profiler:使用async-profiler对应用采样,生成火焰图
- jfr:动态开启关闭 JFR 记录
async-profiler 项目是一个低开销的 Java 采样分析器,不受安全点偏差问题的影响。用于收集堆栈跟踪和跟踪内存分配。该分析器与 OpenJDK 和其他基于 HotSpot JVM 的 Java 运行时一起工作。
Async-profiler 可以跟踪以下类型的事件:
- CPU周期
- 硬件和软件性能计数器,如缓存错误,分支错误,页面错误,上下文切换等。
- Java堆中的分配
- 满足的锁尝试,包括 Java 对象监视器和 ReentrantLocks
5.6、鉴权
- auth:鉴权,启动 Arthas 的时候可以使用
--username
、--password
设置用户名和密码,
5.7、auth
在启动 Arthas 的时候可以使用时,可以在命令行指定密码。比如:
java -jar arthas-boot.jar --password ppp
- 可以通过
--username
选项来指定用户,默认值是“arthas”。 - 也可以在 arthas.properties 里中配置 username/password。命令行的优先级大于配置文件。
- 如果只配置 username,没有配置 password,则会生成随机密码,打印在
~/logs/arthas/arthas.log
中
5.8、options
- options:查看或设置 Arthas 全局开关
5.9、管道
Arthas 支持使用管道对上述命令的结果进行进一步的处理,如sm java.lang.String * | grep 'index'
- grep:搜索满足条件的结果
- plaintext:将命令的结果去除 ANSI 颜色
- wc:按行统计输出结果
5.10、后台异步任务
当线上出现偶发的问题,比如需要 watch 某个条件,而这个条件一天可能才会出现一次时,异步后台任务就派上用场了
- 使用
>
将结果重写向到日志文件,使用&
指定命令是后台运行,session 断开不影响任务执行(生命周期默认为 1 天) - jobs:列出所有 job
- kill:强制终止任务
- fg:将暂停的任务拉到前台执行
- bg:将暂停的任务放到后台执行
6、其他特性
6.1、Web Console
Arthas 目前支持 Web Console,用户在 attach 成功之后,可以直接访问:http://127.0.0.1:8563/。
可以填入 IP,远程连接其它机器上的 arthas。
默认情况下,arthas 只 listen 127.0.0.1,所以如果想从远程连接,则可以使用
--target-ip
参数指定 listen 的 IP,更多参考-h
的帮助说明。 注意会有安全风险,考虑下面的 tunnel server 的方案。
默认 Web Console 支持向上回滚的行数是 1000。可以在 URL 里用scrollback指定。比如
http://127.0.0.1:8563/?scrollback=3000
6.2、启动应用时增加 Java Agent
通常 Arthas 是以动态 attach 的方式来诊断应用,但从3.2.0版本起,Arthas 支持直接以 java agent 的方式启动。
比如下载全量的 arthas zip 包,解压之后以 -javaagent
的参数指定 arthas-agent.jar 来启动:
java -javaagent:/path/to/arthas-agent.jar -jar math-game.jar
默认的配置项在解压目录里的arthas.properties文件里。
6.3、Arthas Tunnel
通过 Arthas Tunnel Server/Client 来远程管理/连接多个 Agent。
比如,在流式计算里,Java 进程可以是在不同的机器启动的,想要使用 Arthas 去诊断会比较麻烦,因为用户通常没有机器的权限,即使登陆机器也分不清是哪个 Java 进程。
在这种情况下,可以使用 Arthas Tunnel Server/Client。
6.3.1、下载部署 arthas tunnel server
- 从 Maven 仓库下载:https://arthas.aliyun.com/download/arthas-tunnel-server/latest_version?mirror=aliyun
- 从 Github Releases 页下载: https://github.com/alibaba/arthas/releases
Arthas tunnel server 是一个 spring boot fat jar 应用,直接java -jar
启动:
java -jar arthas-tunnel-server.jar
默认情况下,arthas tunnel server 的 web 端口是 8080,arthas agent 连接的端口是7777。
启动之后,可以访问 http://127.0.0.1:8080/ ,再通过agentId连接到已注册的 arthas agent 上。
通过 Spring Boot 的 Endpoint,可以查看到具体的连接信息: http://127.0.0.1:8080/actuator/arthas ,登陆用户名是 arthas,密码在 arthas tunnel server 的日志里可以找到。