Arthas -问题诊断神器

背景

出现线上故障,问题在测试环境难以复现(非必现),开发人员人肉定位问题慢,效率低下(无法跟测试环境一样直接 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 ] 指定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 就可以反编译指定 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有一定基础的同学 ......

你可能感兴趣的:(Arthas -问题诊断神器)