背景
出现线上故障,问题在测试环境难以复现(非必现),开发人员人肉定位问题慢,效率低下(无法跟测试环境一样直接 debug),短时间无法找出问题根源。影响比较紧急,业务方群里频繁反馈(或许想拉几个程序员祭天)。此时,拥有屠龙之技傍身的你何愁无处施展?大吼一声放开那"妹子"让我来,一顿操作猛如虎。分分钟就找到问题。让那群菜鸡开发人员无地自容甚至跪下唱征服?
Arthas(阿尔萨斯) 能为你做什么?
Alibaba开源的Java诊断工具,深受开发者喜爱。当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从JVM内查找某个类的实例?
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
快速安装
使用arthas-boot(推荐),下载arthas-boot.jar,然后用java -jar的方式启动,启动之后按一次回车:
安装:curl -O https://arthas.aliyun.com/arthas-boot.jar
启动:java -jar arthas-boot.jar
[root@xxx-7b98666fbc-g79sx core]$ java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.1.5
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 83 com.alibaba.dubbo.container.Main
[2]: 158 qunar.tc.bistoury.indpendent.agent.Main
[INFO] arthas home: /root/.arthas/lib/3.5.5/arthas
[INFO] Try to attach process 83
[INFO] Attach process 83 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 3.5.5
main_class
pid 83
time 2022-01-20 23:18:45
jvm相关
- dashboard——当前系统的实时数据面板
- thread——查看当前 JVM 的线程堆栈信息
- getstatic——查看类的静态属性
dashboard
参数名称 | 参数说明 |
---|---|
[i:] | 刷新实时数据的时间间隔 (ms),默认5000ms |
[n:] | 刷新实时数据的次数 |
数据说明
- ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应。
- NAME: 线程名
- GROUP: 线程组名
- PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
- STATE: 线程的状态
- CPU%: 线程的cpu使用率。比如采样间隔1000ms,某个线程的增量cpu时间为100ms,则cpu使用率=100/1000=10%
- DELTA_TIME: 上次采样之后线程运行增量CPU时间,数据格式为秒
- TIME: 线程运行总CPU时间,数据格式为分:秒
- INTERRUPTED: 线程当前的中断位状态
- DAEMON: 是否是daemon线程
$ dashboard -i 3000 -n 1
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTE DAEMON
-1 C2 CompilerThread0 - -1 - 1.55 0.077 0:8.684 false true
53 Timer-for-arthas-dashboard-07b system 5 RUNNABLE 0.08 0.004 0:0.004 false true
22 scheduling-1 main 5 TIMED_WAI 0.06 0.003 0:0.287 false false
-1 C1 CompilerThread0 - -1 - 0.06 0.003 0:2.171 false true
-1 VM Periodic Task Thread - -1 - 0.03 0.001 0:0.092 false true
49 arthas-NettyHttpTelnetBootstra system 5 RUNNABLE 0.02 0.001 0:0.156 false true
16 Catalina-utility-1 main 1 TIMED_WAI 0.0 0.000 0:0.029 false false
-1 G1 Young RemSet Sampling - -1 - 0.0 0.000 0:0.019 false true
17 Catalina-utility-2 main 1 WAITING 0.0 0.000 0:0.025 false false
34 http-nio-8080-ClientPoller main 5 RUNNABLE 0.0 0.000 0:0.016 false true
23 http-nio-8080-BlockPoller main 5 RUNNABLE 0.0 0.000 0:0.011 false true
-1 VM Thread - -1 - 0.0 0.000 0:0.032 false true
-1 Service Thread - -1 - 0.0 0.000 0:0.006 false true
-1 GC Thread#5 - -1 - 0.0 0.000 0:0.043 false true
Memory used total max usage GC
heap 36M 70M 4096M 0.90% gc.g1_young_generation.count 12
g1_eden_space 6M 18M -1 33.33% 86
g1_old_gen 30M 50M 4096M 0.74% gc.g1_old_generation.count 0
g1_survivor_space 491K 2048K -1 24.01% gc.g1_old_generation.time(ms) 0
nonheap 66M 69M -1 96.56%
codeheap_'non-nmethods' 1M 2M 5M 22.39%
metaspace 46M 47M -1 98.01%
Runtime
os.name Mac OS X
os.version 10.15.4
java.version 15
java.home /Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home
systemload.average 10.68
processors 8
uptime 272s
thread
参数名称 | 参数说明 |
---|---|
id | 线程id |
[n:] | 指定最忙的前N个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i <value>] | 指定cpu使用率统计的采样间隔,单位为毫秒,默认值为200 |
[--all] | 显示所有匹配的线程 |
[-- state] | 线上指定状态匹配的线程 |
[arthas@84]$ thread
Threads Total: 261, NEW: 0, RUNNABLE: 36, BLOCKED: 0, WAITING: 131, TIMED_WAITING: 85, TERMINATED: 0, Internal threads: 9
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTED DAEMON
331 arthas-command-execute system 5 RUNNABLE 1.91 0.003 0:0.009 false true
-1 C1 CompilerThread1 - -1 - 1.03 0.002 0:4.244 false true
24 com.alibaba.nacos.client.Worker.fixed-172.30 main 5 TIMED_WAITING 0.23 0.000 0:0.826 false true
73 I-scheduler-1 main 5 TIMED_WAITING 0.21 0.000 0:0.564 false false
59 erlang-apm-reporter-0 main 5 TIMED_WAITING 0.17 0.000 0:0.496 false true
-1 C2 CompilerThread0 - -1 - 0.08 0.000 0:22.251 false true
6 org.jacoco.agent.rt.internal_43f5073.output. main 5 RUNNABLE 0.08 0.000 0:0.073 false true
277 DubboResponseTimeoutScanTimer main 5 TIMED_WAITING 0.07 0.000 0:0.068 false true
-1 VM Periodic Task Thread - -1 - 0.04 0.000 0:0.108 false true
38 server-timer main 5 TIMED_WAITING 0.02 0.000 0:0.002 false true
58 Abandoned connection cleanup thread main 5 TIMED_WAITING 0.02 0.000 0:0.048 false true
145 ClientHouseKeepingService main 5 TIMED_WAITING 0.02 0.000 0:0.009 false true
83 ClientHouseKeepingService main 5 TIMED_WAITING 0.01 0.000 0:0.007 false true
136 New I/O client worker #1-2 main 5 RUNNABLE 0.01 0.000 0:0.016 false true
当没有参数时,显示第一页线程的信息,默认按照CPU增量时间降序排列,只显示第一页数据。
thread ID :列出指定ID线程,可以从 dashboard 中获取ID
[arthas@84]$ thread 24
"com.alibaba.nacos.client.Worker.fixed-172.30.4.32_8848-18617388-78f3-4a99-aab2-7462d8b6973e" Id=24 TIMED_WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@137b06de
at sun.misc.Unsafe.park(Native Method)
- waiting on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@137b06de
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
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
thread --state waiting : 匹配指定状态的线程
[arthas@28114]$ thread --state WAITING
Threads Total: 16, NEW: 0, RUNNABLE: 9, BLOCKED: 0, WAITING: 3, TIMED_WAITING: 4, TERMINATED: 0
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTE DAEMON
3 Finalizer system 8 WAITING 0.0 0.000 0:0.000 false true
20 arthas-UserStat system 9 WAITING 0.0 0.000 0:0.001 false true
14 arthas-timer system 9 WAITING 0.0 0.000 0:0.000 false true
jad
jad--反编译指定已加载类的源码,将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑
tips: 当不确定线上的代码是否部署的最新时可以查看代码对比即可
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
[c:] | 类所属 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
反编译时只显示源代码
[arthas@93]$ jad --source-only org.example.entity.Record
/*
* Decompiled with CFR.
*/
package org.example.entity;
import org.example.enums.CaseType;
public class Record {
private final String commitId;
/**
* 应用名称
*/
private final String appName;
/**
* 分支
*/
private final String branch;
/**
* 运行环境
*/
private final String env;
/**
* 录制标题
*/
private final String recordTitle;
/**
* 录制任务类型:如:接口自动化测试
*/
private final CaseType recordType;
public String getCommitId() { return commitId; }
public String getAppName() {
return appName;
}
......
反编译指定的函数
[arthas@93]$ jad org.example.entity.Record getCommitId
...省略... 跟上面类似,只是展示对应的函数代码
反编译时指定ClassLoader
$ jad org.apache.log4j.Logger
Found more than one class for: org.apache.log4j.Logger, Please use jad -c hashcode org.apache.log4j.Logger
HASHCODE CLASSLOADER
69dcaba4 +-monitor's ModuleClassLoader
6e51ad67 +-java.net.URLClassLoader@6e51ad67
+-sun.misc.Launcher$AppClassLoader@6951a712
+-sun.misc.Launcher$ExtClassLoader@6fafc4c2
2bdd9114 +-pandora-qos-service's ModuleClassLoader
4c0df5f8 +-pandora-framework's ModuleClassLoader
Affect(row-cnt:0) cost in 38 ms.
$ jad org.apache.log4j.Logger -c 69dcaba4
ClassLoader:
+-monitor's ModuleClassLoader
Location:
/Users/admin/app/log4j-1.2.14.jar
package org.apache.log4j;
import org.apache.log4j.spi.*;
public class Logger extends Category
{
private static final String FQCN;
protected Logger(String name)
{
super(name);
}
...
Affect(row-cnt:1) cost in 190 ms.
当有多个 ClassLoader 都加载了这个类时,jad 命令会输出对应 ClassLoader 实例的 hashcode,然后你只需要重新执行 jad 命令,并使用参数 -c <hashcode> 就可以反编译指定 ClassLoader 加载的那个类了。
watch(推荐)
命令 watch——方法执行数据观测,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 stop 命令。
watch 让你能方便的观察到指定函数的调用情况(推荐)。能观察到的范围为:返回值、抛出异常、入参。
参数说明
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 函数名表达式匹配 |
express | 观察表达式,默认值:{params, target, returnObj} |
condition-express | 条件表达式 |
[b] | 在函数调用之前观察 |
[e] | 在函数异常之后观察 |
[s] | 在函数返回之后观察 |
[f] | 在函数结束之后(正常返回和异常返回)观察 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[x:] | 指定输出结果的属性遍历深度,默认为 1 |
特别说明:
- watch 命令定义了4个观察事件点,即(-b 函数调用前 -e 函数异常后 -s 函数返回后 -f 函数结束后)
- 4个观察事件点 -f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
- 这里要注意函数入参和出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表函数入参外,其余事件都代表函数出参
- 当使用 -b 时,由于观察事件点是在函数调用前,此时返回值或异常均不存在
- 在watch命令的结果里,会打印出 location 信息。location有三种可能值:AtEnter,AtExit,AtExceptionExit。对应函数入口,函数正常 return,函数抛出异常
示例
观察函数调用返回时的参数、this对象、返回值
注意:express 观察表达式,默认值是{params, target, returnObj}
[arthas@93]$ watch org.demo.compensate.CompensateWriteImpl create -x 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 286 ms, listenerId: 3
method=org.demo.compensate.CompensateWriteImpl.create location=AtExit
ts=2022-01-20 09:40:20; [cost=264.505867ms] result=@ArrayList[
@Object[][
@CompensateCreateAo[CompensateCreateAo(uniqueId=657cc750cae44a8aac288d5a4ace7f01, compensateMode=0, compensateAccountType=0, moneyValidTime=400, priceMoney=53.01, compensateMoney=12.0, compensateTotalMoney=12.0, compensateDesc=12, compensateImages=[http://image.baidu.com/7dfa4d7b8eb3e5184fa32736395d689d.png], batchNo=null)],
],
@CompensateWriteProviderImpl[
log=@Log4jLoggerAdapter[org.slf4j.impl.Log4jLoggerAdapter(org.demo.compensate.CompensateWriteImpl)],
domainService=@CompensateDomainService[org.demo.tian.compensates.domain.CompensateDomainService@20b614e],
orderServiceProxy=@OrderServiceProxy[org.demo.tian.compensates.infrastructure.service.proxy.OrderServiceProxy@56089ff9],
compensatePriceQueryService=@CompensatePriceQueryService[org.demo.tian.compensates.infrastructure.service.CompensatePriceQueryService@4de2237],
returnCompensateServiceProxy=@ReturnCompensateServiceProxy[org.demo.tian.compensates.infrastructure.service.proxy.ReturnCompensateServiceProxy@61f93b2],
compensateQueryRepository=@CompensateQueryRepository[org.demo.tian.compensates.infrastructure.repository.CompensateQueryRepository@743a90e1],
compensateQueryService=@CompensateQueryService[org.demo.tian.compensates.infrastructure.service.CompensateQueryService@25960369],
compensateDao=@$Proxy74[org.apache.ibatis.binding.MapperProxy@7ad8f798],
afterImageSignService=@AfterImageSignServiceImpl[org.demo.butian.service.sameimage.AfterImageSignServiceImpl@4ef0e837],
orderReturnService=@proxy7[com.alibaba.dubbo.common.bytecode.proxy7@6d8dabbe],
xyUserLabelProvider=@XyUserLabelProviderImpl[org.demo.butian.provider.XyUserLabelProviderImpl@78206104],
orderReturnServiceProxy=@OrderReturnServiceProxy[org.demo.tian.compensates.infrastructure.service.proxy.OrderReturnServiceProxy@fdb1e5c],
userServiceProxy=@UserServiceProxy[org.demo.tian.compensates.infrastructure.service.proxy.UserServiceProxy@515d351e],
logisticsCostRuleProvider=@proxy37[com.alibaba.dubbo.common.bytecode.proxy37@a0da1e0],
redisCache=@ClusterRedis[org.demo.base.redis.ClusterRedis@249864b0],
priceAlreadyApplyService=@[org.demo.compensate.CompensateWriteImpl$1@2ec15f95],
freightAlreadyApplyService=@[org.demo.compensate.CompensateWriteImpl$2@139819e2],
checkPriceService=@ArrayList[isEmpty=false;size=1],
checkFreightService=@ArrayList[isEmpty=false;size=1],
$jacocoData=@boolean[][isEmpty=false;size=388],
],
@CompensateRo[
extend=null,
$jacocoData=@boolean[][isEmpty=false;size=25],
code=@Integer[0],
msg=@String[],
data=@CompensateDataRo[CompensateRo.CompensateDataRo(compensateNo=543873894546260715)],
DEFAULT_SUCCESS_MESSAGE=@String[请求成功],
$jacocoData=@boolean[][isEmpty=false;size=41],
],
]
分析一下上面的结果:
- 可以看出result是一个ArrayList[Object [][@CompensateCreateAo, @CompensateWriteProviderImpl, @CompensateRo]],分别对应express {params, target, returnObj}
- 上面的示例中 location=AtExit 表示结果正确返回了,如果location=AtExceptionExit,说明函数抛出异常了,returnObj可能就是null。
- 输入的条件表达式 - x 2 表示遍历的深度为3,默认是1.
观察函数调用入口的参数和返回值
- -b 表示在事件之前观察函数入参信息,这时不关注结果返回。
[arthas@93]$ watch org.demo.compensate.CompensateWriteImpl create -x 2 -b
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 319 ms, listenerId: 4
method=org.demo.compensate.CompensateWriteImpl.create location=AtEnter
ts=2022-01-20 09:53:50; [cost=0.019688ms] result=@ArrayList[
@Object[][
@CompensateCreateAo[CompensateCreateAo(uniqueId=e299d35616bc4ae3b914b65601807939, compensateMode=0, compensateAccountType=0, moneyValidTime=400, priceMoney=53.01, compensateMoney=13.0, compensateTotalMoney=13.0, compensateDesc=13, compensateImages=[http://image.baidu.com/7dfa4d7b8eb3e5184fa32736395d689d.png], batchNo=null)],
],
@CompensateWriteProviderImpl[
log=@Log4jLoggerAdapter[org.slf4j.impl.Log4jLoggerAdapter(org.demo.compensate.CompensateWriteImpl)],
domainService=@CompensateDomainService[org.demo.butian.compensate.domain.CompensateDomainService@20b614e],
orderServiceProxy=@OrderServiceProxy[org.demo.butian.compensate.infrastructure.service.proxy.OrderServiceProxy@56089ff9],
compensatePriceQueryService=@CompensatePriceQueryService[org.demo.butian.compensate.infrastructure.service.CompensatePriceQueryService@4de2237],
returnCompensateServiceProxy=@ReturnCompensateServiceProxy[org.demo.butian.compensate.infrastructure.service.proxy.ReturnCompensateServiceProxy@61f93b2],
compensateQueryRepository=@CompensateQueryRepository[org.demo.butian.compensate.infrastructure.repository.CompensateQueryRepository@743a90e1],
compensateQueryService=@CompensateQueryService[org.demo.butian.compensate.infrastructure.service.CompensateQueryService@25960369],
compensateDao=@$Proxy74[org.apache.ibatis.binding.MapperProxy@7ad8f798],
afterImageSignService=@AfterImageSignServiceImpl[org.demo.butian.service.sameimage.AfterImageSignServiceImpl@4ef0e837],
orderReturnService=@proxy7[com.alibaba.dubbo.common.bytecode.proxy7@6d8dabbe],
xyUserLabelProvider=@XyUserLabelProviderImpl[org.demo.butian.provider.XyUserLabelProviderImpl@78206104],
orderReturnServiceProxy=@OrderReturnServiceProxy[org.demo.butian.compensate.infrastructure.service.proxy.OrderReturnServiceProxy@fdb1e5c],
userServiceProxy=@UserServiceProxy[org.demo.butian.compensate.infrastructure.service.proxy.UserServiceProxy@515d351e],
logisticsCostRuleProvider=@proxy37[com.alibaba.dubbo.common.bytecode.proxy37@a0da1e0],
redisCache=@ClusterRedis[org.demo.base.redis.ClusterRedis@249864b0],
priceAlreadyApplyService=@[org.demo.compensate.CompensateWriteImpl$1@2ec15f95],
freightAlreadyApplyService=@[org.demo.compensate.CompensateWriteImpl$2@139819e2],
checkPriceService=@ArrayList[isEmpty=false;size=1],
checkFreightService=@ArrayList[isEmpty=false;size=1],
$jacocoData=@boolean[][isEmpty=false;size=388],
],
null,
]
对比上一个示例,此时返回值为空(事件点为函数执行前,因此获取不到返回值)
同时观察函数调用前和函数返回后
- 当想观察函数调用前合结果返回之间的差异时
[arthas@93]$ watch org.demo.compensate.CompensateWriteImpl create -x 2 -b -s -n 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 281 ms, listenerId: 5
method=org.demo.compensate.CompensateWriteImpl.create location=AtEnter
ts=2022-01-20 09:58:12; [cost=0.015069ms] result=@ArrayList[
@Object[][
@CompensateCreateAo[CompensateCreateAo(uniqueId=04c849a33e324ba09ce1d8f95bb1b0db, compensateMode=0, compensateAccountType=0, moneyValidTime=400, priceMoney=53.01, compensateMoney=14.0, compensateTotalMoney=14.0, compensateDesc=14, compensateImages=[http://image.baidu.com/7dfa4d7b8eb3e5184fa32736395d689d.png], batchNo=null)],
],
@CompensateWriteProviderImpl[
log=@Log4jLoggerAdapter[org.slf4j.impl.Log4jLoggerAdapter(org.demo.compensate.CompensateWriteImpl)],
domainService=@CompensateDomainService[org.demo.compensate.domain.CompensateDomainService@20b614e],
orderServiceProxy=@OrderServiceProxy[org.demo.compensate.infrastructure.service.proxy.OrderServiceProxy@56089ff9],
compensatePriceQueryService=@CompensatePriceQueryService[org.demo.compensate.infrastructure.service.CompensatePriceQueryService@4de2237],
returnCompensateServiceProxy=@ReturnCompensateServiceProxy[org.demo.compensate.infrastructure.service.proxy.ReturnCompensateServiceProxy@61f93b2],
compensateQueryRepository=@CompensateQueryRepository[org.demo.compensate.infrastructure.repository.CompensateQueryRepository@743a90e1],
compensateQueryService=@CompensateQueryService[org.demo.compensate.infrastructure.service.CompensateQueryService@25960369],
compensateDao=@$Proxy74[org.apache.ibatis.binding.MapperProxy@7ad8f798],
afterImageSignService=@AfterImageSignServiceImpl[org.demo.service.sameimage.AfterImageSignServiceImpl@4ef0e837],
orderReturnService=@proxy7[com.alibaba.dubbo.common.bytecode.proxy7@6d8dabbe],
xyUserLabelProvider=@XyUserLabelProviderImpl[org.demo.provider.XyUserLabelProviderImpl@78206104],
orderReturnServiceProxy=@OrderReturnServiceProxy[org.demo.compensate.infrastructure.service.proxy.OrderReturnServiceProxy@fdb1e5c],
userServiceProxy=@UserServiceProxy[org.demo.compensate.infrastructure.service.proxy.UserServiceProxy@515d351e],
logisticsCostRuleProvider=@proxy37[com.alibaba.dubbo.common.bytecode.proxy37@a0da1e0],
redisCache=@ClusterRedis[com.yunji.base.redis.ClusterRedis@249864b0],
priceAlreadyApplyService=@[org.demo.compensate.CompensateWriteImpl$1@2ec15f95],
freightAlreadyApplyService=@[org.demo.compensate.CompensateWriteImpl$2@139819e2],
checkPriceService=@ArrayList[isEmpty=false;size=1],
checkFreightService=@ArrayList[isEmpty=false;size=1],
$jacocoData=@boolean[][isEmpty=false;size=388],
],
null,
]
method=org.demo.compensate.CompensateWriteImpl.create location=AtExit
ts=2022-01-20 09:58:12; [cost=4.71715649799504E10ms] result=@ArrayList[
@Object[][
@CompensateCreateAo[CompensateCreateAo(uniqueId=04c849a33e324ba09ce1d8f95bb1b0db, compensateMode=0, compensateAccountType=0, moneyValidTime=400, priceMoney=53.01, compensateMoney=14.0, compensateTotalMoney=14.0, compensateDesc=14, compensateImages=[http://image.baidu.com/7dfa4d7b8eb3e5184fa32736395d689d.png], batchNo=null)],
],
@CompensateWriteProviderImpl[
log=@Log4jLoggerAdapter[org.slf4j.impl.Log4jLoggerAdapter(org.demo.compensate.CompensateWriteImpl)],
domainService=@CompensateDomainService[org.demo.compensate.domain.CompensateDomainService@20b614e],
orderServiceProxy=@OrderServiceProxy[org.demo.compensate.infrastructure.service.proxy.OrderServiceProxy@56089ff9],
compensatePriceQueryService=@CompensatePriceQueryService[org.demo.compensate.infrastructure.service.CompensatePriceQueryService@4de2237],
returnCompensateServiceProxy=@ReturnCompensateServiceProxy[org.demo.compensate.infrastructure.service.proxy.ReturnCompensateServiceProxy@61f93b2],
compensateQueryRepository=@CompensateQueryRepository[org.demo.compensate.infrastructure.repository.CompensateQueryRepository@743a90e1],
compensateQueryService=@CompensateQueryService[org.demo.compensate.infrastructure.service.CompensateQueryService@25960369],
compensateDao=@$Proxy74[org.apache.ibatis.binding.MapperProxy@7ad8f798],
afterImageSignService=@AfterImageSignServiceImpl[org.demo.service.sameimage.AfterImageSignServiceImpl@4ef0e837],
orderReturnService=@proxy7[com.alibaba.dubbo.common.bytecode.proxy7@6d8dabbe],
xyUserLabelProvider=@XyUserLabelProviderImpl[org.demo.provider.XyUserLabelProviderImpl@78206104],
orderReturnServiceProxy=@OrderReturnServiceProxy[org.demo.compensate.infrastructure.service.proxy.OrderReturnServiceProxy@fdb1e5c],
userServiceProxy=@UserServiceProxy[org.demo.compensate.infrastructure.service.proxy.UserServiceProxy@515d351e],
logisticsCostRuleProvider=@proxy37[com.alibaba.dubbo.common.bytecode.proxy37@a0da1e0],
redisCache=@ClusterRedis[com.yunji.base.redis.ClusterRedis@249864b0],
priceAlreadyApplyService=@[org.demo.compensate.CompensateWriteImpl$1@2ec15f95],
freightAlreadyApplyService=@[org.demo.compensate.CompensateWriteImpl$2@139819e2],
checkPriceService=@ArrayList[isEmpty=false;size=1],
checkFreightService=@ArrayList[isEmpty=false;size=1],
$jacocoData=@boolean[][isEmpty=false;size=388],
],
@CompensateRo[
extend=null,
$jacocoData=@boolean[][isEmpty=false;size=25],
code=@Integer[0],
msg=@String[],
data=@CompensateDataRo[CompensateRo.CompensateDataRo(compensateNo=543882889013084907)],
DEFAULT_SUCCESS_MESSAGE=@String[请求成功],
$jacocoData=@boolean[][isEmpty=false;size=41],
],
]
Command execution times exceed limit: 2, so command will exit. You can set it with -n option.
- 参数里 -n 2,表示只执行两次,从上面的结果观察发现有两次结果记录
- 这里输出结果中,第一次输出的是函数调用前的观察表达式的结果,第二次输出的是函数返回后的表达式的结果
- 结果的输出顺序和事件发生的先后顺序一致,和命令中 -s -b 的顺序无关
条件表达式的例子
- 查询入参满足某个指定条件时
[arthas@93]$ watch org.demo.compensate.CompensateWriteImpl create "params[0]=0"
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 268 ms, listenerId: 6
method=org.demo.compensate.CompensateWriteImpl.create location=AtExit
ts=2022-01-20 10:15:38; [cost=250.521304ms] result=@Integer[0]
- 只有满足条件params [1] = 0 的调用,才会有响应。
观察异常信息的例子
当想观察某个指定异常信息时
$ watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 62 ms.
ts=2018-12-03 19:38:00; [cost=1.414993ms] result=@ArrayList[
@Integer[-1120397038],
java.lang.IllegalArgumentException: number is: -1120397038, need >= 2
at demo.MathGame.primeFactors(MathGame.java:46)
at demo.MathGame.run(MathGame.java:24)
at demo.MathGame.main(MathGame.java:16)
,
]
- -e表示抛出异常时才触发
- express中,表示异常信息的变量是throwExp
watch 还有很多其他的操作......
vmtool
vmtool get instances invoke method field(推荐)
利用JVMTI接口,实现查询内存对象,强制GC等功能(这个是直接去触发调用对象,而watch是观察对象。一个是主动一个是被动), 在线上定位某个对象调用的时候非常方便。
[arthas@84]$ vmtool --action getInstances --className org.demo.CompensateProviderImpl --express 'instances[0].getCompensateAppealInfo(13092L,"code")' --limit 5
@CompensateAppealVo[
appealId=@Long[13092],
appealNo=@Long[0],
orderId=@String[DMPP71188700778259296256],
storeCode=@String[DM089545],
compensateId=@Long[543193216242532498],
compensateTime=@Date[2022-01-19 11:07:57,000],
status=@Integer[20],
appealApplyTime=@Date[2022-01-19 11:09:18,000],
appealClosedTime=null,
appealSuccessTime=null,
appealAmount=@BigDecimal[5.00],
yunjiAmount=@BigDecimal[0.00],
providerAmount=@BigDecimal[5.00],
customServiceAmount=@BigDecimal[0.00],
logisticsAmount=@BigDecimal[0.00],
warehouseAmount=@BigDecimal[0.00],
rejectCount=@Integer[0],
reasonOne=@Integer[1],
reasonTwo=@Integer[0],
imgSet=@String[http://image.baidu.com/1d86f25e74d005149c6722707e26c2ab.png],
remark=@String[1],
statusValue=@String[已申诉待仲裁],
storeName=@String[虐心卖场型旗舰店],
reasonOneName=@String[金额不认可],
reasonTwoName=null,
countdownTime=@Long[0],
compensateTotalMoney=@Double[5.0],
appealBearSide=@String[],
barcode=@String[POPB19137206],
itemName=@String[小米12Pro],
itemModel=@String[N销售属性-内存:16 N销售属性-机型颜色:白色],
buyCount=@Integer[0],
itemId=@Integer[1008688],
itemPrice=@Double[30.9],
itemImg=@String[http://image.baidu.com/admin_dev/e6efe753d2e137c95190102fda069181.jpg?imageView2/1/w/300/h/300],
popAppealPrivilege=@Boolean[false],
videoUrl=@String[http://static.baidu.com/admin_dev/1ef44932788743d35622d392074c83ad.mp4],
videoImg=@String[http://static.baidu.com/admin_dev/1ef44932788743d35622d392074c83ad.mp4?vframe/jpg/offset/1],
businessType=@String[CP],
businessTypeStr=@String[补偿],
businessId=@String[543193216242532498],
$jacocoData=@boolean[][isEmpty=false;size=83],
]
- 通过 --limit 参数,可以限制返回值数量,避免获取超大数据时对JVM造成压力。默认值是10。
以上就是平时比较常用到的一些命令用法,当然 arthas 还有很多强大的功能,有兴趣的可以去查阅:
Arthas Documentation
插件 arthas
以上你可能觉得命令太多,记不住。没关系,idea已经集成了插件,可以一键生成命令表达式。soEasy!
Jetbrains 插件获取地址: https://plugins.jetbrains.com/plugin/11386-alibaba-cloud-toolkit
或者直接在idea安装插件plus
最后
合理的使用工具能够提升工作效率,但是不能过度依赖工具,应该关注问题背后涉及的底层逻辑。
此篇内容仅适用于对Java有一定基础的同学 ......