掘金 后端 ( ) • 2024-04-27 09:43

theme: smartblue

Arthas中文用户手册:https://arthas.gitee.io/doc/quick-start.html

Arthas在线教程:https://arthas.aliyun.com/doc/arthas-tutorials.html?language=cn&id=arthas-basics

使用arthas可以帮助我们解决啥问题

场景 如何使用 线上代码更新后想看看自己修改的类有没有生效 class反编译:jad --source-only com.demo.Service > /tmp/Service.java 如果项目上线后发现有个java文件没有及时提交导致线上没有更新 重新加载class文件:retransform /tmp/MathGame.class 如果想观察某方法在调用前后的入参、出参、返回值和异常信息 同时观察方法调用前和方法返回后watch demo.MathGame run "{params,target,returnObj}" -x 3 -b -s 查看某个静态属性值 getstatic -c 46fbb2c1 demo.MathGame random 调用spring bean的findUserById(),入参为1 vmtool --action getInstances --className org.springframework.context.ApplicationContext --express 'instances[0].getBean("userController").findUserById(1)' 直接获取 UserController 并调用findUserById(),入参为2 vmtool --action getInstances --className com.example.demo.arthas.user.UserController --express 'instances[0].findUserById(2)' 查看方法调用的(入/出)参/返回值/异常信息 见增强命令:watch 如果想看某个方法被调用后,其内部调用链路和耗时 trace demo.MathGame run run()如果执行耗时超过10ms就输出其内部调用链路 trace demo.MathGame run '#cost > 10' 设置HelloService中的静态变量name的值 ognl -x 3 '#[email protected]@class.getDeclaredField("name"),#field.setAccessible(true),#field.set(null,"matio")' -c 1efbd816 try-catch了异常又不打印日志,这种问题如何排查 如果应用部署在docker中咋办

arthas idea插件

推荐一个:arthas idea plugin 使用文档

在IDEA的Marketplace中直接安装arthas idea,也可以选择去idea插件库下载,arthas支持的功能见下图: image.png 来自:https://github.com/WangJi92/arthas-idea-plugin/blob/master/README.md

image.png

启动arthas

test@ubuntucloudtest1:~/arthas-demo$ java -jar arthas-boot.jar
[INFO] JAVA_HOME: /usr/local/jdk1.8.0_111/jre
[INFO] arthas-boot version: 3.7.2
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 13954 math-game.jar
  • 执行该程序的用户需要和目标进程具有相同的权限。比如以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 打印更多参数信息。

输入1后会进入该程序的主控制台

1
[INFO] local lastest version: 3.5.4, remote lastest version: 3.7.2, try to download from remote.
[INFO] Start download arthas from remote server: https://arthas.aliyun.com/download/3.7.2?mirror=aliyun
[INFO] File size: 17.84 MB, downloaded size: 9.99 MB, downloading ...
[INFO] Download arthas success.
[INFO] arthas home: /home/shuncom/.arthas/lib/3.7.2/arthas
[INFO] Try to attach process 13954
Picked up JAVA_TOOL_OPTIONS: 
^A[INFO] Attach process 13954 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.7.2                                                                
main_class                                                                      
pid        13954                                                                
time       2024-04-23 13:28:20                                                  

[arthas@13954]$ 

执行dashboard,会打印当前进程的信息,按ctrl+c可以中断执行,如下:

image.png

退出arthas

用 exit 或者 quit 命令可以退出 Arthas。退出之后,还可以再次用 java -jar arthas-boot.jar 来连接。exit 或者 quit 命令只是退出当前session,arthas server 还在目标进程中运行

想完全退出 Arthas,可以执行 stop 命令。

arthas日志

~/.arthas/~/logs/arthas

arthas命令详解

系统命令

1. dashboard

dashboard -h

dashboard 间隔5s打印当前进程的信息

dashboard -i 3000 间隔3s打印当前进程的信息

dashboard -n 2 -i 1000

ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTED DAEMON -1 C1 CompilerThread3 - -1 - 0.0 0.000 0:1.396 false true
  • 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 线程
Memory used total max usage GC heap 114M 471M 6983M 1.64% gc.ps_scavenge.count 0 ps_eden_space 114M 123M 2577M 4.44% gc.ps_scavenge.time(ms) 0 ps_survivor_space 0K 20992K 20992K 0.00% gc.ps_marksweep.count 0 ps_old_gen 0K 335872K 5363200K 0.00% gc.ps_marksweep.time(ms) 0 nonheap 27M 28M -1 96.87% code_cache 5M 5M 240M 2.36% metaspace 19M 19M -1 96.87% compressed_class_space 2M 2M 1024M 0.23% direct 0K 0K - 200.00% mapped 0K 0K - 0.00%

运行时环境变量

Runtime os.name Linux os.version 4.2.0-27-generic java.version 1.8.0_111 java.home /usr/local/jdk1.8.0_111/jre systemload.average 0.25 processors 10 timestamp/uptime Tue Apr 23 13:33:17 CST 2024/342s

JVM 内部线程:Java 8 之后支持获取 JVM 内部线程 CPU 时间,这些线程只有名称和 CPU 时间,没有 ID 及状态等信息(显示 ID 为-1)。 通过内部线程可以观测到 JVM 活动,如 GC、JIT 编译等占用 CPU 情况,方便了解 JVM 整体运行状况。

  • 当 JVM 堆(heap)/元数据(metaspace)空间不足或 OOM 时,可以看到 GC 线程的 CPU 占用率明显高于其他的线程。
  • 当执行trace/watch/tt/redefine等命令后,可以看到 JIT 线程活动变得更频繁。因为 JVM 热更新 class 字节码时清除了此 class 相关的 JIT 编译结果,需要重新编译。

JVM 内部线程包括下面几种:

  • JIT 编译线程: 如 C1 CompilerThread0C2 CompilerThread0
  • GC 线程: 如GC Thread0G1 Young RemSet Sampling
  • 其它内部线程: 如VM Periodic Task ThreadVM ThreadService Thread

2. thread 查看当前线程信息,查看线程的堆栈

支持一键展示当前最忙的前 N 个线程并打印堆栈:thread -n 3

  • 没有线程 ID,包含[Internal] 表示为 JVM 内部线程,参考dashboard 命令的介绍。
  • cpuUsage 为采样间隔时间内线程的 CPU 使用率,与dashboard 命令的数据一致。
  • deltaTime 为采样间隔时间内线程的增量 CPU 时间,小于 1ms 时被取整显示为 0ms。
  • time 线程运行总 CPU 时间。

注意:线程栈为第二采样结束时获取,不能表明采样间隔时间内该线程都是在处理相同的任务。建议间隔时间不要太长,可能间隔时间越大越不准确。 可以根据具体情况尝试指定不同的间隔时间,观察输出结果。

默认按照 CPU 增量时间降序排列,只显示第一页数据,避免滚屏:thread

显示所有匹配线程信息,有时需要获取全部 JVM 的线程数据进行分析:thread --all

查看线程 ID 16 的运行堆栈:thread 16

找出当前阻塞其他线程的线程:thread -b。有时候我们发现应用卡住了,通常是由于某个线程拿住了某个锁,并且其他线程都在等待这把锁造成的。为了排查这类问题,arthas 提供了thread -b,一键找出那个罪魁祸首。注意,目前只支持找出 synchronized 关键字阻塞住的线程,如果是java.util.concurrent.Lock ,目前还不支持。

统计最近 1000ms 内的线程 CPU 时间:thread -i 1000

列出 1000ms 内最忙的 3 个线程栈:thread -n 3 -i 1000

查看指定状态的线程:thread --state WAITING

3. jvm 查看当前jvm信息

打印出JVM 的各种详细信息:jvm

查找 Java 应用的 classpath:jvm | grep PATH

jvm | grep COUNT

  • COUNT: JVM 当前活跃的线程数
  • DAEMON-COUNT: JVM 当前活跃的守护线程数
  • PEAK-COUNT: 从 JVM 启动开始曾经活着的最大线程数
  • STARTED-COUNT: 从 JVM 启动开始总共启动过的线程次数
  • DEADLOCK-COUNT: JVM 当前死锁的线程数
  • MAX-FILE-DESCRIPTOR-COUNT:JVM 进程最大可以打开的文件描述符数
  • OPEN-FILE-DESCRIPTOR-COUNT:JVM 当前打开的文件描述符数

4. sysprop、sysenv

sysprop 可以打印所有的系统属性信息。而sysenv 命令可以获取到环境变量。两者用法类似

读取单个 key: sysprop java.version

通过grep来过滤: sysprop | grep user

设置新的 value: sysprop testKey testValue

读书系统属性数量: sysprop | wc -l

5. vmoption

查看,更新 VM 诊断相关的参数

查看所有的 option:vmoption

6. vmtool 利用JVMTI接口,实现查询内存对象,强制GC等功能

vmtool在线教程

一般在使用vmtool的时候都是结合arthas idea一起使用的,因为其命令太过复杂,容易出错

支持的参数如下:

 -a, --action <value>                            Action to execute                                                                               
 -c, --classloader <value>                       The hash code of the special class's classLoader                                                
     --classLoaderClass <value>                  The class name of the special class's classLoader.                                              
     --className <value>                         The class name                                                                                  
 -x, --expand <value>                            指定结果的返回层级,默认1                                                           
     --express <value>                           The ognl expression, default value is `instances`.                                              
 -h, --help                                      this help                                                                                       
     --libPath <value>                           The specify lib path.                                                                           
 -l, --limit <value>                             Set the limit value of the getInstances action, default value is 10, set to -1 is unlimited     
 -t, --threadId <value>                          The id of the thread to be interrupted 
package com.example.demo.arthas;
// 省略import...
@Controller
public class WelcomeController {
   @Value("${application.message:Hello World}")
   private String message = "Hello World";
   @GetMapping("/")
   public String welcome(Map<String, Object> model) {
      model.put("time", new Date());
      model.put("message", this.message);
      return "welcome";
   }
   @RequestMapping("/hello")
   public String helloWorld(Model model) {
      model.addAttribute("name", "jsp");
      return "hello";
   }
}

在成功安装好arthas idea插件后,鼠标悬浮到类名、属性名或方法名上的时候点击右键,会出现Arthas Command,其右侧有很多选项,跟vmtool有关的有2个:

  • Vmtool Get Instance Invoke Method Field:调用方法、设置属性
  • Vmtool Get Instance Set Field:修改属性,不支持final属性

vmtool -x 3 --action getInstances --className com.example.demo.arthas.WelcomeController --express 'instances[0].message' -c 1efbd816

给controller中的message属性赋个值:vmtool -x 3 --action getInstances --className com.example.demo.arthas.WelcomeController --express '#field=instances[0].getClass().getDeclaredField("message"),#field.setAccessible(true),#field.set(instances[0],"想要设置的属性值")' -c 1efbd816

解释:

  • -x 3:返回参数展开层级的,默认1,如果返回体是对象,建议设置3,方便观察返回结果,可取值1-4
  • --action getInstances:执行的动作,获取实例,固定不变即可
  • --className com.example.demo.arthas.WelcomeController:指明目标类
  • --express '#field=instances[0].getClass().getDeclaredField("message"),#field.setAccessible(true),#field.set(instances[0]," ")':执行动作:instances[0]代表当前类实例对象,也就是当前controller对象,然后通过反射设置message的值
  • -c 1efbd816:指定加载WelcomeController这个类的classloader的hashcode,这个值需要用户通过sc -d com.example.demo.arthas.WelcomeController | grep classLoaderHash 实时获取

鼠标悬浮到message属性上,右击选择Arthas Command,继续选择Vmtool Get Instance Set Field,会弹出如下窗口: image.png

标题 对应生成的命令 功能 copy sc command sc -d com.example.demo.arthas.WelcomeController 获取类信息,上面有个空的框,需要填入WelcomeController类的classloader的hashcode,就可以直接通过该命令获取 在使用下面的2个命令前需要调用上面的命令拿到WelcomeController的classloader的hashcode,然后填充在上面的空格中。假如hashcode是1efbd816,那么下面的2个命令在生成后会额外加上-c 1efbd816这个选项 copy instantces command vmtool -x 1 --action getInstances --className com.example.demo.arthas.WelcomeController --limit 5 获取该类的所有实例信息(包括类的属性信息),如果jvm中有很多个该类的实例,只会输出前5个 copy invoke command vmtool -x 3 --action getInstances --className com.example.demo.arthas.WelcomeController --express '#field=instances[0].getClass().getDeclaredField("message"),#field.setAccessible(true),#field.set(instances[0]," ")' 反射给WelcomeController类的message属性赋值

举例说明

在执行以下命令前,最好通过sc -d命令获取到指定类的classloader的hashcode,然后-c hascode拼接到以下命令中

例子 vmtool命令 强制gc vmtool --action forceGc 查看类信息,包括属性 vmtool --action getInstances --className demo.MathGame --limit 10 调用实例方法 vmtool --action getInstances --className demo.MathGame --express 'instances[0].primeFactors(3)' -x 3 查找String类型的对象,默认只看前10个。limit如果为-1,则返回所有 vmtool --action getInstances --className java.lang.String -limit 10 查找 spring context vmtool --action getInstances --className org.springframework.context.ApplicationContext 查找所有的 spring beans 名字 vmtool --action getInstances --className org.springframework.context.ApplicationContext --express 'instances[0].getBeanDefinitionNames()' 调用userController.findUserById(),入参为1 vmtool --action getInstances --className org.springframework.context.ApplicationContext --express 'instances[0].getBean("userController").findUserById(1)' 直接获取 UserController 并调用findUserById(),入参为2 vmtool --action getInstances --className com.example.demo.arthas.user.UserController --express 'instances[0].findUserById(2)'

7. logger 查看logger信息,更新logger level

例子 命令 查看所有logger信息 logger 查看指定名字的 logger 信息 logger -n org.springframework.web 更新 logger level logger --name ROOT --level debug

8. getstatic 查看类的静态属性

例子 命令 查看类MathGame的静态属性random getstatic -c 46fbb2c1 demo.MathGame random

-c 46fbb2c1是通过sc -d demo.MathGame命令获取到该类的classloader的hashcde的值

9. ognl 执行 ognl 表达式

在 Arthas 里ognl 表达式是很重要的功能,在很多命令里都可以使用ognl 表达式。一些更复杂的用法可以参考:

# Arthas进阶教程

# OGNL 特殊用法请参考

# OGNL 表达式官方指南

# Arthas问题排查集】活用ognl表达式

例子 命令 读取列表第一个元素 ognl "{1,2,3,4}[0]" ognl "{10,20,30}[0].(#this > 5 ? #this*2 : #this+10)" ognl "{1,2,3,4}.size()" ognl "@java.lang.System@out.(print('Hello '), print('world\n'))" ognl "1 in {2, 3}" ognl "new int[9]" 执行多行表达式,赋值给临时变量,返回一个 List ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}' 获取静态类的静态字段 ognl -c 目标类所属classloader的hashcode @com.example.demo.arthas.user.UserController@logger 获取静态类的静态字段,且返回结果展开3层 ognl -c 目标类所属classloader的hashcode -x 3 @com.example.demo.arthas.user.UserController@logger 设置HelloService中的静态变量name的值 ognl -x 3 '#[email protected]@class.getDeclaredField("name"),#field.setAccessible(true),#field.set(null,"matio")' -c 1efbd816

10. heapdump

dump java heap, 类似 jmap 命令的 heap dump 功能。

例子 命令 dump 到临时文件 heapdump dump 到指定文件 heapdump /tmp/dump.hprof 只 dump live 对象 heapdump --live /tmp/dump.hprof

类命令

1. sc 读取class信息

“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息

读取指定class的classloader的hashcode:sc -d demo.Controller | grep classLoaderHash

如果是接口,还会搜索所有的实现类:sc javax.servlet.Filter

通过-d 参数,可以打印出类加载的具体信息:sc -d javax.servlet.Filter

支持通配,比如搜索所有的StringUtils :sc *StringUtils

打印出类的 Field 信息:sc -d -f demo.MathGame

2. sm 读取method信息

“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到

通过-d 参数可以打印方法的具体属性:sm -d java.math.RoundingMode

查找构造函数:sm java.math.RoundingMode <init>

查找指定方法并打印详细信息:sm -d java.lang.String toString

3. jad 反编译class

jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;如需批量下载指定包的目录的 class 字节码可以参考dump

反编译时只显示源代码:jad --source-only demo.MathGame

反编译指定函数:jad demo.MathGame main

反编译时不显示行号:jad demo.MathGame main --lineNumber false

反编译指定classloader加载的那个类:jad -c 目标class所属classloader的hashcode org.apache.log4j.Logger。我们可以通过sc -d org.apache.log4j.Logger | grep classLoaderHash获取指定类所属classloader的hashcode

jad 反编译的结果保存在/tmp/UserController.java本地文件:jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java

4. mc java编译class

Memory Compiler/内存编译器,编译.java 文件生成.class 。编译生成.class 文件之后,可以结合retransform 命令实现热更新代码。

mc 命令有可能失败。如果编译失败,可以在本地编译好.class文件,再上传到服务器。具体参考retransform

编译java文件保存到默认位置:mc /home/demo/Test1.java

编译多个java文件保存到默认位置:mc /home/demo/Test1.java /home/arthas-demo/Test2.java

使用指定classloader编译java文件保存到默认位置:mc -c 42a57993 /home/demo/Test1.java /home/demo/Test2.java

使用指定classloader编译java文件保存到指定位置:mc -c 42a57993 -d /home/demo/test/ /home/demo/Test1.java /home/demo/Test2.java

5. retransform 热更新class文件

加载外部的.class文件,需要保证该class已经被加载了

如果项目上线后发现有个java文件没有及时提交导致线上没有更新,这时我们可以使用该命令热更新该文件,但是有限制如下:

  • 不允许新增加 field/method
  • 正在跑的函数,没有退出不能生效

重新加载class文件:retransform /tmp/MathGame.class

加载指定的 .class 文件,然后解析出 class name,再 retransform jvm 中已加载的对应的类。每加载一个 .class 文件,则会记录一个 retransform entry。如果多次执行 retransform 加载同一个 class 文件,则会有多条 retransform entry.

查看 retransform entry:retransform -l

[arthas@2377]$ retransform -l
Id              ClassName       TransformCount  LoaderHash      LoaderClassName 
1              demo.MathGame    1               null            null      

TransformCount 统计在 ClassFileTransformer#transform 函数里尝试返回 entry 对应的 .class 文件的次数,但并不表明 transform 一定成功。

如果对某个类执行 retransform 之后,想消除影响(就是还原),则需要:

  • 删除这个类对应的 retransform entry
  • 重新触发 retransform

如果不清除掉所有的 retransform entry,并重新触发 retransform ,则 arthas stop 时,retransform 过的类仍然生效。

6. redefine 忽略

推荐使用retransform命令

7. dump 下载bytecode文件

保存已加载类的bytecode到特定目录

dump 命令将 JVM 中实际运行的 class 的 byte code下载到指定目录,适用场景批量下载指定包目录的 class 字节码;如需反编译单一类、实时查看类信息,可参考jad

支持通配符下载:dump demo.*

下载String.class保存到指定目录:dump -d /tmp/output java.lang.String

8. classloader

查看 classloader 的继承树,urls,类加载信息

classloader 命令将 JVM 中所有的 classloader 的信息统计出来,并可以展示继承树,urls 等。可以让指定的 classloader 去 getResources,打印出所有查找到的 resources 的 url。对于ResourceNotFoundException比较有用

查看所有classloader:classloader -l

查看classloader继承树:classloader -t

统计 ClassLoader 实际使用 URL 和未使用的 URL:classloader --url-stat

更多关于classloader的命令:https://arthas.gitee.io/doc/classloader.html

增强命令

1. watch 查看方法调用的(入/出)参/返回值/异常信息

更多关于watch的使用:https://arthas.gitee.io/doc/watch.html 或 https://arthas.aliyun.com/doc/arthas-tutorials.html?language=cn&id=command-watch

 <class-pattern>                                 The full qualified class name you want to watch                                                 
 <method-pattern>                                The method name you want to watch
 -x, --expand <value>                            result中对象的显示层级,默认1,最大为4  
 -n, --limits <value>                            最大执行次数
 <express>                                       观察表达式,默认值:`{params, target, returnObj}` 
                                                 Examples:                                                                                       
                                                   params                                                                                        
                                                   params[0]                                                                                     
                                                   'params[0]+params[1]'                                                                         
                                                   '{params[0], target, returnObj}'                                                              
                                                   returnObj                                                                                     
                                                   throwExp                                                                                      
                                                   target                                                                                        
                                                   clazz                                                                                         
                                                   method                                                                                        
 -b, --before                                    在函数调用之前观察,默认关闭        
 -e, --exception                                 在函数异常之后观察,默认关闭       
 -s, --success                                   在函数返回之后观察,默认关闭        
 -f, --finish                                    在函数结束之后(正常返回和异常返回)观察,默认打开                                             
     --exclude-class-pattern <value>             exclude class name pattern, use either '.' or '/' as separator                                  
                                                                                 
 -h, --help                                      this help                                                                                                                                            
     --listenerId <value>                        The special listenerId                                                                          
 -m, --maxMatch <value>                          The maximum of matched class.                                                                   
 -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)                                                                              
 -v, --verbose                                   Enables print verbose information, default value false.                                            
 <condition-express>                             Conditional expression in ognl style, for example:                                              
                                                   TRUE  : 1==1                                                                                  
                                                   TRUE  : true                                                                                  
                                                   FALSE : false                                                                                 
                                                   TRUE  : 'params.length>=0'                                                                    
                                                   FALSE : 1==2                                                                                  
                                                   '#cost>100'    

在 watch 命令的结果里,会打印出location信息。location有三种可能值:AtEnterAtExitAtExceptionExit。对应函数入口,函数正常 return,函数抛出异常

这里要注意函数入参函数出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表函数入参外,其余事件都代表函数出参

  1. 观察primeFactors()方法调用后的返回值:

watch demo.MathGame primeFactors returnObj

Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 157 ms, listenerId: 1
method=demo.MathGame.primeFactors location=AtExceptionExit
ts=2024-04-23 07:55:59; [cost=0.565772ms] result=null
method=demo.MathGame.primeFactors location=AtExit
ts=2024-04-23 07:56:00; [cost=0.068512ms] result=@ArrayList[
    @Integer[2],
    @Integer[3],
    @Integer[3],
    @Integer[5],
    @Integer[19],
    @Integer[37],
]

因为命令中只观察了primeFactors()的返回值,所以result结果就是该方法返回值。我们就截取了2次调用输出如上所示,第一次result=null说明出现异常了(location=AtExceptionExit),二第二次就正常输出了一个ArrayList

  1. 观察primeFactors()方法出参、this 对象和返回值

watch demo.MathGame primeFactors -x 2

这里的-x 2是指result返回对象只显示最多2层(x默认为1,最大为4)

method=demo.MathGame.primeFactors location=AtExceptionExit
ts=2024-04-23 12:05:56; [cost=0.025534ms] result=@ArrayList[
    @Object[][
        @Integer[-109004],
    ],
    @MathGame[
        random=@Random[java.util.Random@5afa04c],
        illegalArgumentCount=@Integer[339],
    ],
    null,
]
method=demo.MathGame.primeFactors location=AtExceptionExit
ts=2024-04-23 12:05:54; [cost=0.044902ms] result=@ArrayList[
    @Object[][
        @Integer[-163380],
    ],
    @MathGame[
        random=@Random[java.util.Random@5afa04c],
        illegalArgumentCount=@Integer[338],
    ],
    null,
]
method=demo.MathGame.primeFactors location=AtExit
ts=2024-04-23 12:05:55; [cost=0.026476ms] result=@ArrayList[
    @Object[][
        @Integer[1],
    ],
    @MathGame[
        random=@Random[java.util.Random@5afa04c],
        illegalArgumentCount=@Integer[338],
    ],
    @ArrayList[
        @Integer[2],
        @Integer[2],
        @Integer[7],
        @Integer[5011],
    ],
]

这里我们截取了3次结果,通过location知道只有第三次正常执行完成了。而且每个result中返回了3个对象,他们其实依次代表着{params, target, returnObj},也就是方法出参、this对象和返回值

  1. 观察primeFactors()方法入参

watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b

这里的-x 2是指result返回对象只显示最多2层(x默认为1,最大为4)

-b 指的是在primeFactors()方法调用之前观察

method=demo.MathGame.primeFactors location=AtEnter
ts=2024-04-23 12:13:06; [cost=0.010306ms] result=@ArrayList[
    @Object[][
        @Integer[-7315],
    ],
    null,
]
method=demo.MathGame.primeFactors location=AtEnter
ts=2024-04-23 12:13:07; [cost=0.006734ms] result=@ArrayList[
    @Object[][
        @Integer[165981],
    ],
    null,
]
method=demo.MathGame.primeFactors location=AtEnter
ts=2024-04-23 12:13:08; [cost=0.009809ms] result=@ArrayList[
    @Object[][
        @Integer[-140341],
    ],
    null,
]

result中第一个元素就是指方法调用前参数,第二个参数就是返回值,因为这里观察的是方法调用前,所以获取不到返回值

  1. 同时观察方法调用前和方法返回后

watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2

这里的-x 2是指result返回对象只显示最多2层(x默认为1,最大为4)

-b 指的是在primeFactors()方法调用之前观察

-s 指的是在primeFactors()方法调用之后观察

-n 2 表示只执行两次

[arthas@2091]$ watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 29 ms, listenerId: 5
method=demo.MathGame.primeFactors location=AtEnter
ts=2024-04-23 12:20:01; [cost=0.019763ms] result=@ArrayList[
    @Object[][
        @Integer[-12188],
    ],
    @MathGame[
        random=@Random[java.util.Random@5afa04c],
        illegalArgumentCount=@Integer[748],
    ],
    null,
]
method=demo.MathGame.primeFactors location=AtEnter
ts=2024-04-23 12:20:02; [cost=0.008146ms] result=@ArrayList[
    @Object[][
        @Integer[-204858],
    ],
    @MathGame[
        random=@Random[java.util.Random@5afa04c],
        illegalArgumentCount=@Integer[749],
    ],
    null,
]
Command execution times exceed limit: 2, so command will exit. You can set it with -n option.

这里输出结果中,第一次输出的是方法调用前的观察表达式的结果,第二次输出的是方法返回后的表达式的结果 结果的输出顺序和事件发生的先后顺序一致,和命令中 -s -b 的顺序无关

  1. 当条件表达式满足时才会观察第一个出参和返回结果

watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"

method=demo.MathGame.primeFactors location=AtExceptionExit
ts=2024-04-23 12:31:58; [cost=0.0762ms] result=@ArrayList[
    @Integer[-69352],
    @MathGame[demo.MathGame@77cd7a0],
]
method=demo.MathGame.primeFactors location=AtExceptionExit
ts=2024-04-23 12:32:00; [cost=0.022681ms] result=@ArrayList[
    @Integer[-137631],
    @MathGame[demo.MathGame@77cd7a0],
]

params代表出参,params[0]代表第一个出参,"params[0]<0"条件表达式代表当第一个出参小于0的时候才会有观察结果打印出来

只有满足条件的调用,才会有响应。

watch-express 单个值可以不加'{}',多个值需要加'{a,b,c}'。

condition-express 不能加'{}',可以使用逗号分隔子表达式,取表达式最后一个值来判断。

根据参数类型进行过滤(防止方法重载):watch demo.MathGame primeFactors '{params, params[0].class.name}' 'params[0].class.name == "java.lang.Integer"'。前面是被观察的对象,后面是条件表达式

根据参数个数进行过滤:watch demo.MathGame primeFactors '{params, params.length}' 'params.length==1'。前面是被观察的对象,后面是条件表达式

观察异常信息的例子:watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2

  • -e 表示抛出异常时才触发
  • express 中,表示异常信息的变量是throwExp

根据异常类型或者 message 进行过滤:watch demo.MathGame primeFactors '{params, throwExp}' '#msg=throwExp.toString(), #msg.contains("IllegalArgumentException")' -e -x 2

按照耗时进行过滤:watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2#cost>200 (单位是ms ) 表示只有当耗时大于 200ms 时才会输出,这样可以过滤掉执行时间小于 200ms 的调用

观察当前对象中的属性:watch demo.MathGame primeFactors 'target'。如果想查看方法运行前后,当前对象中的属性,可以使用target 关键字,代表当前对象 ,然后使用target.field_name 访问当前对象的某个属性,比如:watch demo.MathGame primeFactors 'target.illegalArgumentCount'

2. monitor 监控某方法的调用次数、成功次数、失败次数等

监控的维度说明

监控项 说明 timestamp 时间戳 class Java 类 method 方法(构造方法、普通方法) total 调用次数 success 成功次数 fail 失败次数 rt 平均 RT fail-rate 失败率 例子 命令 间隔5s输出一次统计结果 monitor -c 5 demo.MathGame primeFactors 计算条件表达式过滤统计结果(方法执行完毕之后) monitor -c 5 demo.MathGame primeFactors "params[0] <= 2" 计算条件表达式过滤统计结果(方法执行完毕之前) monitor -b -c 5 demo.MathGame primeFactors "params[0] <= 2"

3. trace 输出方法内部调用路径,并输出方法路径上的每个节点上耗时

更多关于trace的使用:https://arthas.gitee.io/doc/trace.html

当执行trace demo.MathGame run命令时,如果run()被调用了,那么就会打印run()的内部调用链路和耗时

run()如下:

public void run() throws InterruptedException {
    try {
        int number = random.nextInt() / 10000;
        List<Integer> primeFactors = primeFactors(number);
        print(number, primeFactors);
    } catch (Exception e) {
        System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
    }
}

run()执行了2次,就会打印2次内部调用链路和耗时:

`---ts=2024-04-26 11:25:05;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@46fbb2c1
    `---[0.213813ms] demo.MathGame:run()
        +---[1.99% 0.00425ms ] demo.MathGame:primeFactors() #24
        `---[89.85% 0.192109ms ] demo.MathGame:print() #25

`---ts=2024-04-26 11:25:06;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@46fbb2c1
    `---[0.236956ms] demo.MathGame:run()
        `---[7.69% 0.018218ms ] demo.MathGame:primeFactors() #24 [throws Exception]

默认情况下,trace 不会包含 jdk 里的函数调用,如果希望 trace jdk 里的函数,需要显式设置--skipJDKMethod false

如果我们只想在run()调用耗时超过10ms的时候才输出其内部调用链路,可以这样写: trace demo.MathGame run '#cost > 10'

4. stack 输出当前方法被调用的调用路径

跟trace相反,更多关于stack的可以参考:https://arthas.gitee.io/doc/stack.html

5. tt

6. profiler

7. jfr