Arthas 线上 Debug(Java动态追踪)

前言

有些时候线上突发问题为了不破坏现场,我们需要在线 Debug 调试定位问题

Arthas 介绍

Arthas 是一款阿里开源的 Java 诊断工具可以帮我们解决如下问题:

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

安装

执行如下命令:

1
$ curl -L https://alibaba.github.io/arthas/install.sh | sh

使用

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
# 启动
$ ./as.sh
Arthas script version: 3.0.4
Found existing java process, please choose one and hit RETURN.
* [1]: 22857 app-exec.jar
1
Calculating attach execution time...
Attaching to 24345 using version 3.1.1...
real 0m0.188s
user 0m0.290s
sys 0m0.037s
Attach success.
Connecting to arthas server... current timestamp is 1559982277
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki: https://alibaba.github.io/arthas
version: 3.0.4
pid: 24345
timestamp: 1559982277879
$

常用命令

常规指令
  1. help 查看命令帮助信息
  2. cls 清空当前屏幕区域
  3. session 查看当前会话的信息
  4. reset 重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
  5. version 输出当前目标 Java 进程所加载的 Arthas 版本号
  6. quit 退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
  7. shutdown 关闭 Arthas 服务端,所有 Arthas 客户端全部退出
  8. keymap Arthas快捷键列表及自定义快捷键
JVM 相关指令
  1. dashboard 当前系统的实时数据面板
  2. thread 查看当前 JVM 的线程堆栈信息
  3. jvm 查看当前 JVM 的信息
  4. sysprop 查看和修改JVM的系统属性
  5. getstatic 查看类的静态属性
Class / Classloader 相关
  1. sc 查看JVM已加载的类信息
  2. sm 查看已加载类的方法信息
  3. dump dump 已加载类的 byte code 到特定目录
  4. redefine 加载外部的.class文件,redefine到JVM里
  5. jad 反编译指定已加载类的源码
  6. classloader 查看classloader的继承树,urls,类加载信息,使用classloader去getResource
monitor / watch / trace 相关

这三个命令是通过字节码增强技术来实现,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 shutdown 或将增强过的类执行 reset 命令

  1. monitor 方法执行监控
  2. watch 方法执行数据观测
  3. trace 方法内部调用路径,并输出方法路径上的每个节点上耗时
  4. stack 输出当前方法被调用的调用路径
  5. tt 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
其他
  1. options 查看或设置Arthas全局开关
  2. grep 搜索满足条件的结果
  3. plaintext 将命令的结果去除颜色
  4. wc 按行统计输出结果

除此之外支持 Web Console,通过 websocket 连接 Arthas。

官方手册

介绍一个我认为非常好用的命令 watch

当线上需要监控某个方法内的入参 & 返回值时非常方便好用。

用法如下:

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
60
$ watch --help
USAGE:
watch [-b] [-e] [-x <value>] [-f] [-h] [-n <value>] [-E] [-M <value>] [-s] class-pattern method-pattern express [condition-express]
SUMMARY:
Display the input/output parameter, return object, and thrown exception of specified method invocation
The express may be one of the following expression (evaluated dynamically):
target : the object
clazz : the object's class
method : the constructor or method
params : the parameters array of method
params[0..n] : the element of parameters array
returnObj : the returned object of method
throwExp : the throw exception of method
isReturn : the method ended by return
isThrow : the method ended by throwing exception
#cost : the execution time in ms of method invocation
Examples:
watch -b org.apache.commons.lang.StringUtils isBlank params
watch -f org.apache.commons.lang.StringUtils isBlank returnObj
watch org.apache.commons.lang.StringUtils isBlank '{params, target, returnObj}' -x 2
watch -bf *StringUtils isBlank params
watch *StringUtils isBlank params[0]
watch *StringUtils isBlank params[0] params[0].length==1
watch *StringUtils isBlank params '#cost>100'
watch -E -b org\.apache\.commons\.lang\.StringUtils isBlank params[0]
WIKI:
https://alibaba.github.io/arthas/watch
OPTIONS:
-b, --before Watch before invocation
-e, --exception Watch after throw exception
-x, --expand <value> Expand level of object (1 by default)
-f, --finish Watch after invocation, enable by default
-h, --help this help
-n, --limits <value> Threshold of execution times
-E, --regex Enable regular expression to match (wildcard matching by default)
-M, --sizeLimit <value> Upper size limit in bytes for the result (10 * 1024 * 1024 by default)
-s, --success Watch after successful invocation
<class-pattern> The full qualified class name you want to watch
<method-pattern> The method name you want to watch
<express> the content you want to watch, written by ognl.
Examples:
params
params[0]
'params[0]+params[1]'
'{params[0], target, returnObj}'
returnObj
throwExp
target
clazz
method
<condition-express> Conditional expression in ognl style, for example:
TRUE : 1==1
TRUE : true
FALSE : false
TRUE : 'params.length>=0'
FALSE : 1==2

例如监控这个 Java 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.test.service;
@Component
@Slf4j
public class TestProd {
public Boolean checkSwitch(String password) {
boolean flag = false
if (password.equals("0000000000")) {
flag = true;
}
log.info("开关切换 -> {} -> {}", password, flag);
return flag;
}
}

在这里,我们针对方法 checkSwitch() 返回值进行监测。监测结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 监控返回值
$ watch -f com.test.service.TestProd checkSwitch returnObj
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 93 ms.
ts=2019-06-08 22:20:01; [cost=1.314913ms] result=@Boolean[false]
ts=2019-06-08 22:20:11; [cost=0.345592ms] result=@Boolean[false]
ts=2019-06-08 22:20:13; [cost=2.810705ms] result=@Boolean[false]
ts=2019-06-08 22:20:15; [cost=0.387806ms] result=@Boolean[false]
watch -f com.geely.service.OaCheckInProd checkSwitch params[0]
# 监控入参
$ watch -f com.test.service.TestProd checkSwitch returnObj
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 37 ms.
ts=2019-06-08 22:21:58; [cost=0.948934ms] result=@String[111111]
ts=2019-06-08 22:23:00; [cost=0.3557ms] result=@String[222222]
ts=2019-06-08 22:23:03; [cost=0.3557ms] result=@String[333333]
ts=2019-06-08 22:23:07; [cost=0.3557ms] result=@String[444444]

扩展阅读

Arthas 是基于 JVM Agent 方式,使用 Instrumentation 方式修改字节码方式以及使用 java.lang.management 包提供的管理接口的方式进行 Java 应用诊断。

java.lang.instrument.Instrumentation
文档中有这么两个接口:redefineClasses 重新定义 Class & retransformClasses 重新转换 Class(修改 Class ), redefineClasses 是自己提供字节码文件替换掉已存在的 Class 文件,retransformClasses 是在已存在的字节码文件上修改后再替换之。

java.lang.instrument 包的具体实现,依赖于 JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的,为 JVM 相关的工具提供的本地编程接口集合。JVMTI 提供了一套 "代理"程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。事实上,java.lang.instrument 包的实现,也就是基于这种机制的:在 Instrumentation 的实现当中,存在一个 JVMTI 的代理程序,通过调用 JVMTI 当中 Java 类相关的函数来完成 Java 类的动态操作。

Attach API Java SE 6新特性通过Java Tool API 中的 Attach 方式,我们可以很方便地连接到JVM上,在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。

基于 InstrumentsAttach API 优秀的程序员们开发出了 BTraceArthas 这样的工具。

----本文结束 感谢您的阅读----