Arthas

arthas

Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率.

https://github.com/alibaba/arthas

测试代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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();
    public 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;
    }
}

附加进程

  1. 启动Demo

启动Demo

  1. 执行arthas-boot.jar进行附加
1
java -jar arthas-boot.jar

运行arthas-boot

如果提示端口被占用,可以使用下述命令进行端口自定义.

1
java -jar arthas-boot.jar --telnet-port 9998 --http-port -1

如果附加成功,Arthas会在下述目录进行日志记录.

1
%HOMEPATH%/logs/arthas
  1. 通过浏览器连接Arthas

Arthas目前支持Web Console,用户在attach成功之后,可以直接访问:http://127.0.0.1:3658/

浏览器控制台

默认情况下,Arthas只listen 127.0.0.1,所以如果想从远程连接,则可以使用–target-ip参数指定listen的IP

基础命令

基础命令 说明
base64 base64编码转换.同Linux base64
cat 打印文件内容.同Linux cat
cls 清空当前屏幕区域
echo 打印参数.同Linux echo
grep 匹配查找.同Linux grep
help 查看命令帮助信息
history 打印命令历史
keymap 显示所有的快捷键
pwd 显示当前的工作路径.同Linux pwd
quit 退出当前Arthas客户端,其他Arthas客户端不受影响
reset 重置增强类,将被Arthas增强过的类全部还原,Arthas服务端关闭时会重置所有增强过的类
session 查看当前会话信息
stop 关闭Arthas服务端,所有Arthas客户端全部退出
tee 复制标准输入到标准输出和指定的文件.同Linux tee
version 输出当前目标Java进程所加载的Arthas版本号

JVM命令

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

Class/Classloader命令

类,类加载相关的命令 说明
sc 查看JVM已加载的类信息
sm 查看已加载类的方法信息
jad 反编译字节码为源代码
mc 内存编译器,内存编译.java文件为.class文件
redefine 加载外部的.class文件,redefine到JVM里
retransform 加载外部的.class文件,retransform到JVM里(推荐)
dump dump已加载类的byte code到特定目录
classloader 获取类加载器信息

sc

查看JVM已加载的类信息,“Search-Class"的简写,这个命令能搜索出所有已经加载到JVM中的Class信息.

sc默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,可以执行下述命令.

1
options disable-sub-class true

参数说明如下:

参数名称 参数说明
class-pattern 类名表达式匹配,支持全限定名,如com.luohun.test.AAA,也支持com/luohun/test/AAA这样的格式
method-pattern 方法名表达式匹配
[d] 输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息. 如果一个类被多个ClassLoader所加载,则会出现多次
[E] 开启正则表达式匹配,默认为通配符匹配
[f] 输出当前类的成员变量信息(需要配合参数-d一起使用)

示例命令如下:

1
2
3
4
5
//模糊搜索,demo包下所有的类
sc demo.*

//打印类的详细信息
sc -d demo.MathGame

sc

sm

查看已加载类的方法信息,“Search-Method"的简写,这个命令能搜索出所有已经加载了Class信息的方法信息.但是只能看到由当前类所声明(declaring)的方法,父类则无法看到.

参数说明如下:

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
[d] 展示每个方法的详细信息
[E] 开启正则表达式匹配,默认为通配符匹配

示例命令如下:

1
2
3
4
5
//显示String类加载的方法
sm java.lang.String

//显示String中的toString方法详细信息
sm -d java.lang.String toString

sm

jad

反编译字节码为源代码.

参数说明如下:

参数名称 参数说明
class-pattern 类名表达式匹配
[E] 开启正则表达式匹配,默认为通配符匹配

示例命令如下:

1
2
3
4
5
6
7
8
//编译java.lang.String
jad java.lang.String

//反编绎时只显示源代码,默认情况下,反编译结果里会带有ClassLoader信息,通过--source-only选项,可以只打印源代码.方便和mc/redefine命令结合使用.
jad --source-only demo.MathGame > C:/Users/XiaLuoHun/Desktop/jad/MathGame.java
    
//反编译指定的函数
jad demo.MathGame main

jad

mc

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

示例命令如下:

1
2
3
4
5
//在内存中编译Hello.java为Hello.class
mc C:/Users/XiaLuoHun/Desktop/jad/MathGame.java

//可以通过-d命令指定输出目录
mc -d C:/Users/XiaLuoHun/Desktop/jad C:/Users/XiaLuoHun/Desktop/jad/MathGame.java

mc

redefine

加载外部的.class文件,推荐使用retransform

示例命令如下:

1
2
//使用redefine命令加载新的字节码
redefine C:/Users/XiaLuoHun/Desktop/jad/demo/MathGame.class

redefine的限制如下:

  • 不允许新增加field/method
  • 正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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());
        }
    }
    
}
注意
  • redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field)

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

  • redefine命令和jad/watch/trace/monitor/tt等命令会冲突.执行完redefine之后,如果再执行上面提到的命令,则会把redefine的字节码重置

retransform

加载外部的.class文件

示例命令如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//加载指定的.class文件,然后解析出class name,再retransform jvm中已加载的对应的类
//每加载一个.class 文件,则会记录一个retransform entry
retransform C:/Users/XiaLuoHun/Desktop/jad/demo/MathGame.class
    
//查看retransform entry
retransform -l
    
//删除指定retransform entry
retransform -d 1

//删除所有retransform entry
retransform --deleteAll
    
//显式触发 retransform
retransform --classPattern demo.MathGame

若要消除retransform的影响需进行下述步骤:

  1. 删除这个类对应的retransform entry
  2. 重新触发retransform

retransform的限制如下:

  • 不允许新增加field/method
  • 正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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());
        }
    }
    
}

dump

将已加载类的字节码文件保存到特定目录:%HOMEPATH%/logs/arthas/classdump

参数说明如下:

参数名称 参数说明
class-pattern 类名表达式匹配
[c:] 类所属ClassLoader的hashcode
[E] 开启正则表达式匹配,默认为通配符匹配

示例命令如下:

1
2
//把demo包下所有的类的字节码文件保存到~/logs/arthas/classdump/目录下
dump demo.*

classloader

获取类加载器的信息

参数说明如下:

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

示例命令如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//默认按类加载器的类型查看统计信息
classloader

//按类加载器的实例查看统计信息,可以看到类加载的hashCode
classloader -l
    
//查看ClassLoader的继承树
classloader -t
    
//通过类加载器的hash,查看此类加载器实际所在的位置
classloader -c 798f84d5
    
//使用ClassLoader去查找类的class文件所在的位置
classloader -c 798f84d5 -r java/lang/String.class
    
//使用ClassLoader去加载类
classloader -c 798f84d5 --load java.lang.String

classloader

进阶命令

命令 说明
monitor 监控指定类中方法的执行情况
watch 观察到指定方法的调用情况
trace 对方法内部调用路径进行追踪,并输出方法路径上每个节点上耗时
stack 输出当前方法被调用的路径
tt 记录指定方法每次调用的入参和返回信息
options 全局开关

monitor

监控指定类中方法的执行情况

参数说明如下:

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

示例命令如下:

1
2
//过5秒监控一次,类demo.MathGame中primeFactors方法
monitor -c 5 demo.MathGame primeFactors

monitor

监控项说明如下:

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

watch

观察指定方法的调用情况

参数说明如下:

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
express 观察表达式
condition-express 条件表达式
[b] 方法调用之前观察before
[e] 方法异常之后观察exception
[s] 方法返回之后观察success
[f] 方法结束之后(正常返回和异常返回)观察finish
[E] 开启正则表达式匹配,默认为通配符匹配
[x:] 指定输出结果的属性遍历深度,默认为1
注意
  • watch 命令定义了4个观察事件点,即-b方法调用前,-e方法异常后,-s 方法返回后,-f方法结束后
  • 4个观察事件点-b、-e、-s默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
  • 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了-b事件点params代表方法入参外,其余事件都代表方法出参
  • 当使用-b时,由于观察事件点是在方法调用前,此时返回值或异常均不存在

示例命令如下:

1
2
3
//观察demo.MathGame类中primeFactors方法出参和返回值,结果属性遍历深度为2
//params表示所有参数数组(因为不确定是几个参数),returnObject表示返回值
watch demo.MathGame primeFactors "{params,returnObj}" -x 2

watch1

1
2
//观察方法入参,对比前一个例子,返回值为空(事件点为方法执行前,因此获取不到返回值)
watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b

watch2

1
2
3
4
//同时观察方法调用前和方法返回后,参数里-n 2,表示只执行两次
//这里输出结果中,第一次输出的是方法调用前的观察表达式的结果,第二次输出的是方法返回后的表达式的结果
//params表示参数,target表示执行方法的对象,returnObject表示返回值
watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2

watch3

1
2
//条件表达式的例子,输出第1参数小于的情况
watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"

watch4

trace

对方法内部调用路径进行追踪,并输出方法路径上的每个节点上耗时

参数说明如下:

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

示例命令如下:

1
2
//trace函数指定类的指定方法
trace demo.MathGame run

trace1

1
2
//如果方法调用的次数很多,那么可以用-n参数指定捕捉结果的次数.比如下面的例子里,捕捉到一次调用就退出命令
trace demo.MathGame run -n 1

trace2

1
2
//默认情况下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数,需要显式设置--skipJDKMethod false
trace --skipJDKMethod false demo.MathGame run

trace3

stack

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

参数说明如下:

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

示例命令如下:

1
2
//获取primeFactors的调用路径
stack demo.MathGame primeFactors

stack1

1
2
//条件表达式来过滤,第0个参数的值小于0,-n表示获取2次
stack demo.MathGame primeFactors 'params[0]<0' -n 2

stack2

tt

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

参数说明如下:

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

示例命令如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//记录下当前方法的每次调用环境现场。
tt -t demo.MathGame primeFactors
    
//检索调用记录
tt -l

//筛选出primeFactors方法的调用信息
tt -s 'method.name=="primeFactors"'

//查看调用信息
//-i 参数后边跟着对应的INDEX编号查看到详细信息
tt -i 1002

//重做一次调用
//tt命令由于保存了当时调用的所有现场信息,所以我们可以自己主动对一个INDEX编号的时间片自主发起一次调用,从而解放你的沟通成本
//可以通过--replay-times指定调用次数,通过 `--replay-interval` 指定多次调用间隔(单位ms, 默认1000ms)
tt -i 1002 -p

tt1

tt2

表格字段说明如下:

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

options

全局开关

全局选项如下:

名称 默认值 描述
unsafe false 是否支持对系统级别的类进行增强,打开该开关可能导致把JVM搞挂,请慎重选择!
dump false 是否支持被增强了的类dump到外部文件中,如果打开开关,class文件会被dump到/${application dir}/arthas-class-dump/目录下,具体位置详见控制台输出
batch-re-transform true 是否支持批量对匹配到的类执行retransform操作
json-format false 是否支持json化的输出
disable-sub-class false 是否禁用子类匹配,默认在匹配目标类的时候会默认匹配到其子类,如果想精确匹配,可以关闭此开关
debug-for-asm false 打印ASM相关的调试信息
save-result false 是否打开执行结果存日志功能,打开之后所有命令的运行结果都将保存到~/logs/arthas-cache/result.log中
job-timeout 1d 异步后台任务的默认超时时间,超过这个时间,任务自动停止;比如设置 1d, 2h, 3m, 25s,分别代表天、小时、分、秒
print-parent-fields true 是否打印在parent class里的filed

示例命令如下:

1
2
3
4
5
6
7
8
//查看所有的options
options

//获取option的值
options json-format

//设置指定的option
options save-result true

参考链接

Arthas帮助文档


相关内容

0%