1 Arthas 介绍
1.1 Arthas是什么
Arthas 是Alibaba开源的Java诊断工具。它支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,
进一步方便进行问题的定位和诊断。
Arthas 官方文档十分详细,本文也参考了官方文档内容,同时在开源在的 Github 的项目里的 Issues 里不仅有问题反馈,更有大量的使用案例,也可以进行学习参考。
开源地址:https://github.com/alibaba/arthas
官方文档:https://alibaba.github.io/arthas
1.2 Arthas使用场景
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
是否有一个全局视角来查看系统的运行状况?
有什么办法可以监控到JVM的实时运行状态?
怎么快速定位应用的热点,生成火焰图?
1.3 Arthas怎么用
Arthas 是一款命令行交互模式的 Java 诊断工具,由于是 Java 编写,所以可以直接下载相应 的 jar 包运行。
1.3.1 standalone
- wget https://alibaba.github.io/arthas/arthas-boot.jar
- java -jar arthas-boot.jar执行后,选择需要检测的应用进程id即可
1.3.2 idea plugin
- 以idea为例,在应用市场搜索Alibaba Cloud View并点击安装
- 添加需要检测的host
- 点击more-dignostic,等待一会后,安装成功,选择需要检测的应用进程id即可
2 Arthas 基本使用篇
在了解了什么是 Arthas,以及 Arthas 的启动方式,下面详细介绍 Arthas 的常用使用方式。在使用命令的过程中如果有问题,每个命令都可以是 -h 查看帮助信息。
首先编写一个有各种情况的测试类运行起来,再使用 Arthas 进行问题定位。下面代码模拟了cpu过高,线程阻塞以及死锁。完整代码可以访问https://github.com/pj1987111/hongyinotes/tree/main/hongyiarthas获取。
@Slf4j
public class Problems {
private static int CPU_THREADS = 10;
private static ExecutorService executorService = Executors.newFixedThreadPool(CPU_THREADS);
public static void start() {
// 模拟 CPU 过高
cpu();
// 模拟线程阻塞
thread();
// 模拟线程死锁
deadThread();
}
/**
* 模拟cpu高损耗
*/
private static void cpu() {
for (int i = 0; i < CPU_THREADS; i++) {
executorService.submit(new Thread(() -> {
while (true) {
cpurun();
}
}));
}
}
/**
* 只有完整退出的方法才可以被热更新
*/
private static void cpurun() {
log.info("cpu start");
// try {
// Thread.sleep(10000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
/**
* 模拟线程阻塞,向已经满了的线程池提交线程
*/
private static void thread() {
// 添加到线程
executorService.submit(new Thread(() -> {
while (true) {
log.debug("thread start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}));
}
/**
* 死锁
*/
private static void deadThread() {
Object resourceA = new Object();
Object resourceB = new Object();
Thread threadA = new Thread(() -> {
synchronized (resourceA) {
log.info(Thread.currentThread() + " get ResourceA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(Thread.currentThread() + "waiting get resourceB");
synchronized (resourceB) {
log.info(Thread.currentThread() + " get resourceB");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (resourceB) {
log.info(Thread.currentThread() + " get ResourceB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(Thread.currentThread() + "waiting get resourceA");
synchronized (resourceA) {
log.info(Thread.currentThread() + " get resourceA");
}
}
});
threadA.start();
threadB.start();
}
}
2.1 全局监控
首先可以执行dashboard
命令,可以监控内存,GC,线程,运行环境等,如下图所示。
2.2 线程状态监控
2.2.1 CPU 状态监控
根据代码,可以看到cpu()方法是一个死循环打印,非常占用cpu。
输入thread
查看线程的CPU占用情况,将通过CPU占比倒序输出线程情况,可以看到前10个线程总计cpu占用100%。
[arthas@3599812]$ thread
Threads Total: 41, NEW: 0, RUNNABLE: 12, BLOCKED: 2, WAITING: 23, TIMED_WAITING: 4, TERMINATED: 0
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
33 pool-1-thread-2 main 5 RUNNABLE 10 0:18 false false
43 pool-1-thread-7 main 5 WAITING 10 0:18 false false
45 pool-1-thread-8 main 5 WAITING 10 0:18 false false
47 pool-1-thread-9 main 5 WAITING 10 0:18 false false ```
然后可以输入thread 线程id
查看线程的堆栈信息,可以迅速定位到高cpu损耗的代码位置。
[arthas@3599812]$ thread 33
"pool-1-thread-2" Id=33 WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@77efb74e
at sun.misc.Unsafe.park(Native Method)
- waiting on java.util.concurrent.locks.ReentrantLock$NonfairSync@77efb74e
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.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at ch.qos.logback.core.OutputStreamAppender.writeBytes(OutputStreamAppender.java:197)
at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:231)
at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270)
at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257)
at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:421)
at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
at ch.qos.logback.classic.Logger.info(Logger.java:579)
at com.hy.arthas.Problems.cpurun(Problems.java:48)
at com.hy.arthas.Problems.lambda$cpu$0(Unknown Source)
at com.hy.arthas.Problems$$Lambda$370/551479935.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Number of locked synchronizers = 1
- java.util.concurrent.ThreadPoolExecutor$Worker@1dde4cb2
Affect(row-cnt:0) cost in 150 ms.
如果线上遇到类似问题,如何在不停机的情况下快速处理呢?先卖一个关子,后面介绍。
2.2.2 线程池查看
通过thread | grep pool
命令查看线程池状态,可以看到线程池中有一个WAITING线程。
[arthas@3589152]$ thread | grep pool
31 pool-1-thread-1 main 5 TIMED_WAITING 0 0:0 false false
49 pool-1-thread-10 main 5 TIMED_WAITING 0 0:0 false false
33 pool-1-thread-2 main 5 TIMED_WAITING 0 0:0 false false
35 pool-1-thread-3 main 5 TIMED_WAITING 0 0:0 false false
37 pool-1-thread-4 main 5 TIMED_WAITING 0 0:0 false false
39 pool-1-thread-5 main 5 TIMED_WAITING 0 0:0 false false
41 pool-1-thread-6 main 5 TIMED_WAITING 0 0:0 false false
43 pool-1-thread-7 main 5 TIMED_WAITING 0 0:0 false false
45 pool-1-thread-8 main 5 TIMED_WAITING 0 0:0 false false
47 pool-1-thread-9 main 5 TIMED_WAITING 0 0:0 false false
60 pool-2-thread-1 system 5 WAITING 0 0:0 false false
2.2.3 线程池死锁排查
在之前代码中模拟了一个线程死锁的情况,通过thread --state BLOCKED
命令直接定位死锁线程
[arthas@3589152]$ thread --state BLOCKED
Threads Total: 40, NEW: 0, RUNNABLE: 10, BLOCKED: 2, WAITING: 14, TIMED_WAITING: 14, TERMINATED: 0
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
52 Thread-16 main 5 BLOCKED 51 0:0 false false
51 Thread-15 main 5 BLOCKED 48 0:0 false false
Affect(row-cnt:0) cost in 102 ms.
然后根据线程ID打印堆栈,定位问题代码。
[arthas@3589152]$ thread 52
"Thread-16" Id=52 BLOCKED on java.lang.Object@53687148 owned by "Thread-15" Id=51
at com.hy.arthas.Problems.lambda$deadThread$3(Problems.java:97)
- blocked on java.lang.Object@53687148
- locked java.lang.Object@2a8415ca
at com.hy.arthas.Problems$$Lambda$373/1645547422.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Affect(row-cnt:0) cost in 74 ms.
[arthas@3589152]$ thread 51
"Thread-15" Id=51 BLOCKED on java.lang.Object@2a8415ca owned by "Thread-16" Id=52
at com.hy.arthas.Problems.lambda$deadThread$2(Problems.java:82)
- blocked on java.lang.Object@2a8415ca
- locked java.lang.Object@53687148
at com.hy.arthas.Problems$$Lambda$372/1820383114.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Affect(row-cnt:0) cost in 61 ms.
2.3 classloader相关信息
为了方便后续教程的演示,编写一个springboot-web程序,完整代码可以访问https://github.com/pj1987111/hongyinotes/tree/main/hongyiarthas获取。
ArthasController.java
@RestController
@Slf4j
public class ArthasController {
@Autowired
private ArthasService arthasService;
@RequestMapping(value = "/get")
public Map<String, Object> getInfo(Integer uid) throws Exception {
log.info("get "+uid);
arthasService.get(uid);
Map<String, Object> hashMap = new HashMap<>(1);
hashMap.put("uid", uid);
return hashMap;
}
@RequestMapping(value = "/put")
public void putTest(String uid) {
log.info("put "+uid);
arthasService.putVal(uid);
}
}
ArthasService.java
@Service
@Slf4j
public class ArthasService {
private static List<String> cache = new ArrayList<>();
private List<String> fieldCache = new ArrayList<>();
public void get(Integer uid) throws Exception {
check(uid);
small(uid);
medium(uid);
high(uid);
}
public void small(Integer uid) throws Exception {
int count = 0;
for (int i = 0; i < 10; i++) {
count += i;
}
log.info("small end {}", count);
}
public void medium(Integer uid) throws Exception {
int count = 0;
for (int i = 0; i < 10000; i++) {
count += i;
}
log.info("medium end {}", count);
}
public void high(Integer uid) throws Exception {
long count = 0;
for (int i = 0; i < 10000000; i++) {
count += i;
}
log.info("high end {}", count);
}
public boolean check(Integer uid) throws Exception {
if (uid == null || uid < 0) {
log.error("uid不正确,uid:{}", uid);
throw new Exception("uid不正确");
}
return true;
}
public Map<String, String> putVal(String uid) {
cache.add("uid===>" + uid);
fieldCache.add("local:"+uid);
return cache.stream().collect(Collectors.toMap(String::toString, String::toUpperCase, (key1, key2) -> key2));
}
}
2.3.1 查看方法信息
使用sm
查看类的方法信息
[arthas@3294199]$ sm com.hy.arthas.service.ArthasService
com.hy.arthas.service.ArthasService <init>()V
com.hy.arthas.service.ArthasService small(Ljava/lang/Integer;)V
com.hy.arthas.service.ArthasService medium(Ljava/lang/Integer;)V
com.hy.arthas.service.ArthasService get(Ljava/lang/Integer;)V
com.hy.arthas.service.ArthasService check(Ljava/lang/Integer;)Z
com.hy.arthas.service.ArthasService high(Ljava/lang/Integer;)V
com.hy.arthas.service.ArthasService putVal(Ljava/lang/Integer;)V
Affect(row-cnt:7) cost in 17 ms.
2.3.2 查看类与变量信息
使用sc -d -f
命令获取类的详细信息以及变量信息
[arthas@3625353]$ sc -d -f com.hy.arthas.service.ArthasService
class-info com.hy.arthas.service.ArthasService
code-source file:/home/admin/zhy/arthastest/hongyiarthas-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/
name com.hy.arthas.service.ArthasService
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name ArthasService
modifier public
annotation org.springframework.stereotype.Service
interfaces
super-class +-java.lang.Object
class-loader +-org.springframework.boot.loader.LaunchedURLClassLoader@238e0d81
+-sun.misc.Launcher$AppClassLoader@55f96302
+-sun.misc.Launcher$ExtClassLoader@49594137
classLoaderHash 238e0d81
fields name log
type org.slf4j.Logger
modifier final,private,static
value Logger[com.hy.arthas.service.ArthasService]
name cache
type java.util.List
modifier private,static
value [uid===>2, uid===>3, uid===>4]
name fieldCache
type java.util.List
modifier private
2.3.3 变量访问与控制
使用ognl表达式可以很方便控制变量的行为,多用作静态变量。
- 首先使用curl对应用发送请求get请求
curl --location --request GET 'http://33.69.2.53:3890/put' --form 'uid=3'
- 执行ognl命令获取类静态变量cache的值。
ognl '@com.hy.arthas.service.ArthasService@cache'
如果发生找不到类的错,需要指定classloader,如下所示,可以看到cache值被打印了,重复执行可以看到cache的值增加。
[arthas@3294199]$ sc -d com.hy.arthas.service.ArthasService | grep Hash
classLoaderHash 238e0d81
[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache'
@ArrayList[
@String[uid===>3],
]
curl --location --request GET 'http://cdh219:3890/put' --form 'uid=1'
[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache'
@ArrayList[
@String[uid===>3],
@String[uid===>1],
]
- 使用ognl命令动态变更静态变量
ognl表达式有非常强大的功能,就是可以调用变量的函数。如下所示,可以调用add,clear,size等函数。
[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache.add("test_add")'
@Boolean[true]
[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache'
@ArrayList[
@String[uid===>3],
@String[uid===>1],
@String[test_add],
]
[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache.clear()'
null
[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache.size()'
@Integer[0]
这功能除了解决问题外,感觉还可以应用到很多其他地方,比如根据业务需求临时变更一些变量,以达到管控作用。
- 获取系统环境
ognl表达式的其他用法。
[arthas@3294199]$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
@ArrayList[
@String[/usr/java/jdk1.8.0_171/jre],
@String[Java(TM) SE Runtime Environment],
]
2.3.4 观察方法运行耗时
当程序没问题时,有时候也需要密切观测程序的运行状态,比如方法的rt时间,针对长时间响应的方法做优化。arthas支持每次调用的耗时统计以及统计耗时。
2.3.4.1 方法运行耗时
首先介绍一下方法运行耗时监控。运行trace
命令,trace 类名 方法名
监控函数,然后发送请求
curl --location --request GET 'http://cdh219:3890/get' --form 'uid=4'
可以看到com.hy.arthas.service.ArthasService:get()的方法耗时最长。
[arthas@3294199]$ trace com.hy.arthas.controller.ArthasController getInfo
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 44 ms.
`---ts=2021-01-18 21:29:18;thread_name=http-nio-3890-exec-5;id=15;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@7c417213
`---[10.511479ms] com.hy.arthas.controller.ArthasController:getInfo()
+---[0.693079ms] org.slf4j.Logger:info() #29
`---[9.332481ms] com.hy.arthas.service.ArthasService:get() #30
然后继续定位内部的方法com.hy.arthas.service.ArthasService:get(),然后再次发送请求
curl --location --request GET 'http://cdh219:3890/get' --form 'uid=4'
可以准确定位到high()耗时最长。
[arthas@3294199]$ trace com.hy.arthas.service.ArthasService get
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 40 ms.
`---ts=2021-01-18 21:30:38;thread_name=http-nio-3890-exec-6;id=16;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@7c417213
`---[22.246322ms] com.hy.arthas.service.ArthasService:get()
+---[0.039841ms] com.hy.arthas.service.ArthasService:check() #23
+---[0.368773ms] com.hy.arthas.service.ArthasService:small() #24
+---[0.670825ms] com.hy.arthas.service.ArthasService:medium() #25
`---[18.308464ms] com.hy.arthas.service.ArthasService:high() #26
2.3.4.2 统计方法耗时
对于一些重点方法,需要测试一下调用的压力,以便于找到瓶颈点重点优化。
采用monitor
,可以间隔c秒重复n次的监控,继续发送n次之前的请求,可以看到每隔5秒的调用次数,失败成功数以及平均rt时间。
[arthas@3294199]$ monitor -c 5 -n 3 com.hy.arthas.service.ArthasService get
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 35 ms.
timestamp class method total success fail avg-rt(ms) fail-rate
---------------------------------------------------------------------------------------------------------------
2021-01-18 21:37:16 com.hy.arthas.service.ArthasService get 3 3 0 12.83 0.00%
timestamp class method total success fail avg-rt(ms) fail-rate
---------------------------------------------------------------------------------------------------------------
2021-01-18 21:37:21 com.hy.arthas.service.ArthasService get 3 3 0 9.17 0.00%
timestamp class method total success fail avg-rt(ms) fail-rate
---------------------------------------------------------------------------------------------------------------
2021-01-18 21:37:26 com.hy.arthas.service.ArthasService get 2 2 0 5.76 0.00%
Command execution times exceed limit: 3, so command will exit. You can set it with -n option.
2.3.5 方法执行数据观测
2.3.5.1 方法出入参观测
线上有时候出bug需要快速知晓某个方法的入参和出参,也是可以做到的。
使用 watch
命令轻松查看输入输出参数以及异常等信息。执行后再发送之前的curl命令测试。
#返回入参以及出参
[arthas@3355851]$ watch com.hy.arthas.service.ArthasService putVal '{params[0],returnObj}'
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 43 ms.
ts=2021-01-18 22:27:20; [cost=3.134463ms] result=@ArrayList[
@String[6],
@HashMap[isEmpty=false;size=3],
]
#返回入参以及出参size(Map)
[arthas@3355851]$ watch com.hy.arthas.service.ArthasService putVal '{params[0],returnObj.size()}'
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 43 ms.
ts=2021-01-18 22:28:08; [cost=2.868114ms] result=@ArrayList[
@String[7],
@Integer[4],
]
#打印入参和返回值toString
[arthas@3355851]$ watch com.hy.arthas.service.ArthasService putVal '{params[0],returnObj.toString()}'
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 42 ms.
ts=2021-01-18 22:28:43; [cost=3.754227ms] result=@ArrayList[
@String[10],
@String[{uid===>10=UID===>10, uid===>6=UID===>6, uid===>7=UID===>7, uid===>1=UID===>1, uid===>4=UID===>4}],
]
#打印入参,返回值toString,以及类中成员变量
[arthas@3363085]$ watch com.hy.arthas.service.ArthasService putVal '{params[0],returnObj.toString(),target.fieldCache.toString()}'
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 35 ms.
ts=2021-01-18 22:39:26; [cost=2.939916ms] result=@ArrayList[
@String[2],
@String[{uid===>1=UID===>1, uid===>2=UID===>2}],
@String[[local:1, local:2]],
]
2.3.5.2 方法调用堆栈
使用stack可以查看被执行的方法调用堆栈
[arthas@3363085]$ stack com.hy.arthas.service.ArthasService putVal
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 55 ms.
ts=2021-01-18 22:43:22;thread_name=http-nio-3890-exec-5;id=15;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@a4102b8
@com.hy.arthas.service.ArthasService.putVal()
at com.hy.arthas.controller.ArthasController.putTest(ArthasController.java:39)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
...
2.3.5.3 TimeTunnel
watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。
这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。
于是乎,TimeTunnel 命令就诞生了。
监控函数,发起请求后记录每次调用。
[arthas@3625353]$ tt -t com.hy.arthas.service.ArthasService get
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 51 ms.
INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1003 2021-01-19 18:36:18 17.034313 true false 0x1e4d527b ArthasService get
1004 2021-01-19 18:36:26 13.678166 true false 0x1e4d527b ArthasService get
1005 2021-01-19 18:36:30 8.297877 true false 0x1e4d527b ArthasService get
1006 2021-01-19 18:36:33 8.80053 true false 0x1e4d527b ArthasService get
可以任意访问之前的版本,可以看到每次调用的信息,比如参数返回值是否异常等。
[arthas@3625353]$ tt -i 1003
INDEX 1003
GMT-CREATE 2021-01-19 18:36:18
COST(ms) 17.034313
OBJECT 0x1e4d527b
CLASS com.hy.arthas.service.ArthasService
METHOD get
IS-RETURN true
IS-EXCEPTION false
PARAMETERS[0] @Integer[4]
RETURN-OBJ null
还可以重新发起一次之前的请求,而不用页面请求,更方便当时现场问题的重现。
[arthas@3625353]$ tt -i 1004 -p
RE-INDEX 1004
GMT-REPLAY 2021-01-19 18:38:36
OBJECT 0x1e4d527b
CLASS com.hy.arthas.service.ArthasService
METHOD get
PARAMETERS[0] @Integer[1]
IS-RETURN true
IS-EXCEPTION false
COST(ms) 10.391835
RETURN-OBJ null
Time fragment[1004] successfully replayed 1 times.
3 实例代码
以上所有实例代码可以在https://github.com/pj1987111/hongyinotes/tree/main/hongyiarthas中找到。
4 接下来
如果看完了这些命令使用的话,基本上可以完成所有的关于线上系统排错、诊断的任务了。
接下来请期待关于Arthas的第二篇文章《arthas助力线上问题分析-线上实战篇》希望您能从中获得帮助。