Arthas:一款优秀的Java诊断工具
# Arthas:一款优秀的Java诊断工具
# 一、简介
Arthas 是一款非常优秀的线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并且能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大的提高线上问题的排查效率。
阿里云Arthas (opens new window)、Gitee Arthas (opens new window)
# 二、快速入门
# 1. 启动 math-game
curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar
2
math-game
是一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。
# 2. 启动 arthas
在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败):
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
2
- 执行该程序的用户需要和目标进程具有相同的权限。比如以
admin
用户来执行:sudo su admin && java -jar arthas-boot.jar
或sudo -u admin -EH java -jar arthas-boot.jar
。 - 如果 attach 不上目标进程,可以查看
~/logs/arthas/
目录下的日志。 - 如果下载速度比较慢,可以使用 aliyun 的镜像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
java -jar arthas-boot.jar -h
打印更多参数信息。
选择应用 java 进程:
$ $ java -jar arthas-boot.jar
* [1]: 35542
[2]: 71560 math-game.jar
2
3
math-game
进程是第 2 个,则输入 2,再输入回车/enter
。Arthas 会 attach 到目标进程上,并输出日志:
[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
2
3
4
5
6
7
8
9
10
11
12
13
# 三、常用命令
# 1. 基本命令
# 1.1 dashboard
查看整个进程的运行情况,线程、内存、GC、运行环境信息
# 1.2 thread
thread id, 显示指定线程的运行堆栈
$ thread 1
"main" Id=1 WAITING on java.util.concurrent.CountDownLatch$Sync@29fafb28
at sun.misc.Unsafe.park(Native Method)
- waiting on java.util.concurrent.CountDownLatch$Sync@29fafb28
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
2
3
4
5
6
7
8
9
thread -b, 找出当前阻塞其他线程的线程
thread -i, 指定采样时间间隔
thread -i 1000
: 统计最近 1000ms 内的线程 CPU 时间。thread -n 3 -i 1000
: 列出 1000ms 内最忙的 3 个线程栈
$ thread -n 3 -i 1000
"as-command-execute-daemon" Id=4759 cpuUsage=23% RUNNABLE
at sun.management.ThreadImpl.dumpThreads0(Native Method)
at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:440)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:133)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:79)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:96)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:27)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:125)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:122)
at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:332)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:756)
Number of locked synchronizers = 1
- java.util.concurrent.ThreadPoolExecutor$Worker@546aeec1
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1.3 jad 反编译指定已加载类的源码
默认情况下,反编译结果里会带有ClassLoader
信息,通过--source-only
选项,可以只打印源代码。方便和mc (opens new window)/retransform (opens new window)命令结合使用。
$ jad --source-only demo.MathGame
/*
* Decompiled with CFR 0_132.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
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;
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1.4 执行 ognl 表达式
打印类的详细信息
$ sc -d com.example.demo.util.UserUtil
class-info com.example.demo.util.UserUtil
code-source /D:/project/20240201demo/spring-mybatis-demo/target/classes/
name com.example.demo.util.UserUtil
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name UserUtil
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@353d0772
classLoaderHash 18b4aac2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
调用静态函数:
#需要带上上一步获取到的classLoaderHash
$ ognl -c 18b4aac2 -x 3 '@com.example.demo.util.UserUtil@get("123")'
@User[
id=null,
name=@String[123],
updateTime=null,
createTime=null,
]
2
3
4
5
6
7
8
# 2. 进阶命令
# 2.1 trace 查看方法内部调用路径,并输出方法路径上的每个节点上耗时
$ trace demo.MathGame run -n 5 --skipJDKMethod false '#cost > 10'
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 41 ms.
`---ts=2018-12-04 01:12:02;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
`---[12.033735ms] demo.MathGame:run()
+---[0.006783ms] java.util.Random:nextInt()
+---[11.852594ms] demo.MathGame:primeFactors()
`---[0.05447ms] demo.MathGame:print()
2
3
4
5
6
7
8
# 2.2 watch 函数执行数据观测
$ watch demo.MathGame primeFactors -x 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 32 ms, listenerId: 5
method=demo.MathGame.primeFactors location=AtExceptionExit
ts=2021-08-31 15:22:57; [cost=0.220625ms] result=@ArrayList[
@Object[][
@Integer[-179173],
],
@MathGame[
random=@Random[java.util.Random@31cefde0],
illegalArgumentCount=@Integer[44],
],
null,
]
method=demo.MathGame.primeFactors location=AtExit
ts=2021-08-31 15:22:58; [cost=1.020982ms] result=@ArrayList[
@Object[][
@Integer[1],
],
@MathGame[
random=@Random[java.util.Random@31cefde0],
illegalArgumentCount=@Integer[44],
],
@ArrayList[
@Integer[2],
@Integer[2],
@Integer[26947],
],
]
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
- 上面的结果里,说明函数被执行了两次,第一次结果是
location=AtExceptionExit
,说明函数抛出异常了,因此returnObj
是 null - 在第二次结果里是
location=AtExit
,说明函数正常返回,因此可以看到returnObj
结果是一个 ArrayList
# 2.3 tt 时空隧道
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
记录调用
对于一个最基本的使用来说,就是记录下当前方法的每次调用环境现场。
$ tt -t demo.MathGame primeFactors
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 66 ms.
INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD
-------------------------------------------------------------------------------------------------------------------------------------
1000 2018-12-04 11:15:38 1.096236 false true 0x4b67cf4d MathGame primeFactors
1001 2018-12-04 11:15:39 0.191848 false true 0x4b67cf4d MathGame primeFactors
1002 2018-12-04 11:15:40 0.069523 false true 0x4b67cf4d MathGame primeFactors
1003 2018-12-04 11:15:41 0.186073 false true 0x4b67cf4d MathGame primeFactors
1004 2018-12-04 11:15:42 17.76437 true false 0x4b67cf4d MathGame primeFactors
2
3
4
5
6
7
8
9
10
查看调用信息
对于具体一个时间片的信息而言,你可以通过 -i
参数后边跟着对应的 INDEX
编号查看到他的详细信息。
$ tt -i 1003
INDEX 1003
GMT-CREATE 2018-12-04 11:15:41
COST(ms) 0.186073
OBJECT 0x4b67cf4d
CLASS demo.MathGame
METHOD primeFactors
IS-RETURN false
IS-EXCEPTION true
PARAMETERS[0] @Integer[-564322413]
THROW-EXCEPTION java.lang.IllegalArgumentException: number is: -564322413, need >= 2
at demo.MathGame.primeFactors(MathGame.java:46)
at demo.MathGame.run(MathGame.java:24)
at demo.MathGame.main(MathGame.java:16)
Affect(row-cnt:1) cost in 11 ms.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
重做一次调用
当你稍稍做了一些调整之后,你可能需要前端系统重新触发一次你的调用,此时得求爷爷告奶奶的需要前端配合联调的同学再次发起一次调用。而有些场景下,这个调用不是这么好触发的。
tt
命令由于保存了当时调用的所有现场信息,所以我们可以自己主动对一个 INDEX
编号的时间片自主发起一次调用,从而解放你的沟通成本。此时你需要 -p
参数。通过 --replay-times
指定 调用次数,通过 --replay-interval
指定多次调用间隔(单位 ms, 默认 1000ms)
$ tt -i 1004 -p
RE-INDEX 1004
GMT-REPLAY 2018-12-04 11:26:00
OBJECT 0x4b67cf4d
CLASS demo.MathGame
METHOD primeFactors
PARAMETERS[0] @Integer[946738738]
IS-RETURN true
IS-EXCEPTION false
COST(ms) 0.186073
RETURN-OBJ @ArrayList[
@Integer[2],
@Integer[11],
@Integer[17],
@Integer[2531387],
]
Time fragment[1004] successfully replayed.
Affect(row-cnt:1) cost in 14 ms.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.获取JVM中某个对象的内容
#获取tomcat线程池配置
vmtool -x 3 --action getInstances --className org.springframework.boot.autoconfigure.web.ServerProperties --express 'instances[0].getTomcat()'
2
详见:arthas vmtool (opens new window)
# 四、Idea插件-arthas idea的使用
# 1. 安装
# 2. 使用
在代码的类上或方法上右键即可,省去自行拼接类名、方法名等
# 3.通过arthas插件调用静态函数
step1: 在方法右键,如下图
step2: 点击 copy sc command
执行后获取类的classLoaderHash信息
$ sc -d com.example.demo.util.UserUtil
class-info com.example.demo.util.UserUtil
code-source /D:/project/20240201demo/spring-mybatis-demo/target/classes/
name com.example.demo.util.UserUtil
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name UserUtil
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@353d0772
classLoaderHash 18b4aac2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
step3: 点击copy command
复制,调用静态函数
#需要带上上一步获取到的classLoaderHash
$ ognl -c 18b4aac2 -x 3 '@com.example.demo.util.UserUtil@get("123")'
@User[
id=null,
name=@String[123],
updateTime=null,
createTime=null,
]
2
3
4
5
6
7
8
# 五、FAQ
# 5.1、class redefinition failed: attempted to change the scheam
trace demo.MathGame run -n 5 --skipJDKMethod false '#cost > 10'
在生产环境执行trace命令时出现如下报错
java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the scheam (add/remove fields)
问题原因:
arthas在修改类结构的时候发现已经被修改了,所以抛出异常。定位到是因为skywalking兼容arthas的问题,Skywalking探针会对Controller层的方法进行改写,导致arthas 相关命令监控失败,如果需要监控Controller层,则需要关闭Skywalking,service层和mapper层不影响。
解决方法:
skywalking提供两种解决方案(本人暂未验证,参考:skywalking兼容arthas问题 (opens new window))
服务启动的时候开启增强类的缓存功能,添加如下启动参数
-Dskywalking.agent.is_cache_enhanced_class=true -Dskywalking.agent.class_cache_mode=MEMORY
1修改代理类配置文件
agent.conf
agent.is_cache_enhanced_class = ${SW_AGENT_CACHE_CLASS:false} agent.class_cache_mode = ${SW_AGENT_CLASS_CACHE_MODE:MEMORY}
1
2