继上一次学习使用 Arthas 之后,今天特此学习了解下 Arthas 在项目中比较好用的几个命令。

启动 Arthas

首先,使用 as.sh 脚本启动 Arthas ,找到需要监控诊断的 Java 进程。

image-20241120135507933

输入前面的数字索引下标,进入 Java 进程,例如 IwAuthApplication 进程需要输入 3 。

image-20241120135629279

接下来就可以使用 Arthas 命令操作了,如果不清楚有哪些命令,在不方便查看官方文档的情况下,或者想要知道当前版本的最新命令,可以直接输入 help 有哪些命令。

image-20241120135910056

知道主命令之后,可以接上参数 -h 了解每个命令的具体使用方法。

命令列表

quit

退出当前 Arthas 客户端,其他 Arthas 客户端不受影响。等同于exitlogoutq三个指令。

使用 quit 命令,只是退出当前 Arthas 客户端,Arthas 的服务器端并没有关闭,所做的修改也不会被重置。

这里所说的修改是指,因为 Arthas 是以 Java agent 方式运行的,它可以修改指定 Java 进程的参数配置等信息,如果 Arthas 服务器端未关闭,配置就不会重置。

stop

关闭 Arthas 服务端,所有 Arthas 客户端全部退出。

关闭 Arthas 服务器之前,会重置掉所有做过的增强类。但是用 redefine 重加载的类内容不会被重置。

jvm

jvm命令可以查看到当前运行的 Java 进程的 JVM 相关信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
$ jvm
RUNTIME # JVM 运行环境
-------------------------------------------------------------------------------------------------------------------
MACHINE-NAME 47349@192.168.1.4
JVM-START-TIME 2024-11-20 13:45:29
MANAGEMENT-SPEC-VERSION 3.0
SPEC-NAME Java Virtual Machine Specification
SPEC-VENDOR Oracle Corporation
SPEC-VERSION 17
VM-NAME OpenJDK 64-Bit Server VM
VM-VENDOR Homebrew
VM-VERSION 17.0.9+0
INPUT-ARGUMENTS -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:57581,suspend=y,server=n
-XX:TieredStopAtLevel=1
-Dspring.output.ansi.enabled=always
-Dcom.sun.management.jmxremote
-Dspring.jmx.enabled=true
-Dspring.liveBeansView.mbeanDomain
-Dspring.application.admin.enabled=true
-javaagent:/Users/wangfarui/Library/Caches/JetBrains/IntelliJIdea2022.2/captureA
gent/debugger-agent.jar=file:/private/var/folders/wv/0ljm0h256y1_f_fgv2yw62rh000
0gn/T/capture.props # 通过这里可以发现,Idea在通过Debug方式启动Java程序时,会嵌入一个javaagent程序
-Dfile.encoding=UTF-8
CLASS-PATH []
BOOT-CLASS-PATH
LIBRARY-PATH /Users/wangfarui/Library/Java/Extensions:/Library/Java/Extensions:/Network/Libra
ry/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.

-------------------------------------------------------------------------------------------------------------------
CLASS-LOADING # ClassLoader
-------------------------------------------------------------------------------------------------------------------
LOADED-CLASS-COUNT 21095 # 当前类加载数量
TOTAL-LOADED-CLASS-COUNT 21095 # 总共的类加载数量
UNLOADED-CLASS-COUNT 0 # 已卸载的类数量
IS-VERBOSE false

-------------------------------------------------------------------------------------------------------------------
COMPILATION
-------------------------------------------------------------------------------------------------------------------
NAME HotSpot 64-Bit Tiered Compilers
TOTAL-COMPILE-TIME 2875
[time (ms)]

-------------------------------------------------------------------------------------------------------------------
GARBAGE-COLLECTORS # 垃圾收集器
-------------------------------------------------------------------------------------------------------------------
G1 Young Generation name : G1 Young Generation
[count/time (ms)] collectionCount : 20 # G1新生代收集次数
collectionTime : 79 # G1新生代收集时间
G1 Old Generation name : G1 Old Generation
[count/time (ms)] collectionCount : 0 # G1老年代收集次数
collectionTime : 0 # G1老年代收集时间

-------------------------------------------------------------------------------------------------------------------
MEMORY-MANAGERS # 内存管理器
-------------------------------------------------------------------------------------------------------------------
CodeCacheManager CodeCache
Metaspace Manager Metaspace
Compressed Class Space
G1 Young Generation G1 Eden Space
G1 Survivor Space
G1 Old Gen
G1 Old Generation G1 Eden Space
G1 Survivor Space
G1 Old Gen

-------------------------------------------------------------------------------------------------------------------
MEMORY # 内存信息
-------------------------------------------------------------------------------------------------------------------
HEAP-MEMORY-USAGE init : 268435456(256.0 MiB)
[memory in bytes] used : 115123728(109.8 MiB) # 堆已使用的内存
committed : 169869312(162.0 MiB)
max : 4294967296(4.0 GiB) # 堆最大内存 即 -Xmx
NO-HEAP-MEMORY-USAGE init : 2555904(2.4 MiB)
[memory in bytes] used : 136267744(130.0 MiB) # 元空间已使用的内存
committed : 137297920(130.9 MiB)
max : -1(-1 B) # -1表示元空间无限大
PENDING-FINALIZE-COUNT 0

-------------------------------------------------------------------------------------------------------------------
OPERATING-SYSTEM # 操作系统信息
-------------------------------------------------------------------------------------------------------------------
OS Mac OS X
ARCH aarch64
PROCESSORS-COUNT 8
LOAD-AVERAGE 6.015625
VERSION 14.6.1

-------------------------------------------------------------------------------------------------------------------
THREAD
-------------------------------------------------------------------------------------------------------------------
COUNT 114 # JVM 当前活跃的线程数
DAEMON-COUNT 61 # JVM 当前活跃的守护线程数
PEAK-COUNT 134 # 从 JVM 启动开始曾经活着的最大线程数
STARTED-COUNT 676 # 从 JVM 启动开始总共启动过的线程次数
DEADLOCK-COUNT 0 # JVM 当前死锁的线程数

-------------------------------------------------------------------------------------------------------------------
FILE-DESCRIPTOR
-------------------------------------------------------------------------------------------------------------------
MAX-FILE-DESCRIPTOR-COUNT -1 # JVM 进程最大可以打开的文件描述符数
OPEN-FILE-DESCRIPTOR-COUNT -1 # JVM 当前打开的文件描述符数

thread

作用:查看当前线程信息,查看线程的堆栈。

参数名称 参数说明
id 线程 id
[n:] 指定最忙的前 N 个线程并打印堆栈
[b] 找出当前阻塞其他线程的线程
[i <value>] 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200
[--all] 显示所有匹配的线程

直接执行 thread 命令,表示按照 CPU 增量时间降序排列,打印第一页所有线程数据。

thread --state 查看指定状态的线程。例如 thread --state WAITING查询所有等待线程。

memory

作用:查看 JVM 内存信息。

直接执行 memory 命令,结果信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ memory
Memory used total max usage
heap 97M 162M 4096M 2.37%
g1_survivor_space 8M 10M -1 80.16%
g1_eden_space 34M 66M -1 51.52%
g1_old_gen 55M 86M 4096M 1.34%

nonheap 132M 133M -1 99.27%
metaspace 98M 98M -1 99.36%
compressed_class_space 13M 13M 1024M 1.31%
codecache 20M 20M 48M 43.06%
mapped 0K 0K - 0.00%
direct 86M 86M - 100.00%
mapped - 'non-volatile memory' 0K 0K - 0.00%

可以看到主要分为 heapnonheap 两块信息。

stack

作用:输出当前方法被调用的调用路径。

很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。

stack 是一个监控命令,只能监控 stack 命令执行之后的 JVM 运行状态。它主要是监控具体某个类的某个方法,输出该方法的调用链路。

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
[E] 开启正则表达式匹配,默认为通配符匹配
[n:] 执行次数限制
[m <arg>] 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch <arg>]

[E] 是一个观察表达式,主要由 ognl 表达式组成,具体表达式用法可以参考:

例如,执行 stack com.itwray.iw.auth.service.impl.AuthUserServiceImpl loginByPassword,等待有线程触发执行到 AuthUserServiceImpl#loginByPassword 方法时,Arthas 就会打印调用链路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ stack com.itwray.iw.auth.service.impl.AuthUserServiceImpl loginByPassword
Press Q or Ctrl+C to abort.
Affect(class count: 2 , method count: 2) cost in 187 ms, listenerId: 1
ts=2024-11-20 14:36:19.412;thread_name=http-nio-18001-exec-5;id=122;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@2f29e630
@com.itwray.iw.auth.service.impl.AuthUserServiceImpl.loginByPassword()
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at com.itwray.iw.starter.redis.lock.RedisDistributedLockAspect.around(RedisDistributedLockAspect.java:63)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:637)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:627)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at com.itwray.iw.auth.service.impl.AuthUserServiceImpl$$SpringCGLIB$$0.loginByPassword(<generated>:-1)
at com.itwray.iw.auth.controller.AuthLoginController.loginByPassword(AuthLoginController.java:60)
...

需要注意的是,在上面这段命令执行示例中,有一段输出内容如下:

1
Affect(class count: 2 , method count: 2) cost in 187 ms, listenerId: 1

它表示监听到两个符合条件的类,两个符合条件的方法。然而在项目中我是只写了一个AuthUserServiceImpl#loginByPassword方法的,这是因为 Spring 的@Service 对其进行了增强,生成了一个 AuthUserServiceImpl$$SpringCGLIB$$0.loginByPassword 代理类。

watch

watch命令与stack命令一样,也是监控方法运行的,不过它的主要作用是观察方法的调用情况,例如返回值抛出异常入参

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 函数名表达式匹配
express 观察表达式,默认值:{params, target, returnObj}
condition-express 条件表达式
[b] 函数调用之前观察
[e] 函数异常之后观察
[s] 函数返回之后观察
[f] 函数结束之后(正常返回和异常返回)观察
[E] 开启正则表达式匹配,默认为通配符匹配
[x:] 指定输出结果的属性遍历深度,默认为 1,最大值是 4
[m <arg>] 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch <arg>]

特别说明

  • watch 命令定义了 4 个观察事件点,即 -b 函数调用前,-e 函数异常后,-s 函数返回后,-f 函数结束后
  • 4 个观察事件点 -b-e-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
  • 这里要注意函数入参函数出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表函数入参外,其余事件都代表函数出参
  • 当使用 -b 时,由于观察事件点是在函数调用前,此时返回值或异常均不存在
  • 在 watch 命令的结果里,会打印出location信息。location有三种可能值:AtEnterAtExitAtExceptionExit。对应函数入口,函数正常 return,函数抛出异常。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ watch com.itwray.iw.auth.service.impl.AuthUserServiceImpl loginByPassword
Press Q or Ctrl+C to abort.
Affect(class count: 2 , method count: 2) cost in 117 ms, listenerId: 2
method=com.itwray.iw.auth.service.impl.AuthUserServiceImpl.loginByPassword location=AtExit
ts=2024-11-20 14:47:20.846; [cost=116.9355ms] result=@ArrayList[
@Object[][isEmpty=false;size=1],
@AuthUserServiceImpl[com.itwray.iw.auth.service.impl.AuthUserServiceImpl@7aae5a4c],
@UserInfoVo[UserInfoVo(name=wray, tokenName=iwtoken, tokenValue=0a85a7e9-fed8-4be2-b50a-d6c981b81f98, avatar=https://1.com/img/border-collie-8501579_1920.jpg)],
]
method=com.itwray.iw.auth.service.impl.AuthUserServiceImpl$$SpringCGLIB$$0.loginByPassword location=AtExit
ts=2024-11-20 14:47:20.851; [cost=142.679166ms] result=@ArrayList[
@Object[][isEmpty=false;size=1],
@AuthUserServiceImpl$$SpringCGLIB$$0[com.itwray.iw.auth.service.impl.AuthUserServiceImpl@7aae5a4c],
@UserInfoVo[UserInfoVo(name=wray, tokenName=iwtoken, tokenValue=0a85a7e9-fed8-4be2-b50a-d6c981b81f98, avatar=https://1.com/img/border-collie-8501579_1920.jpg)],
]

注意:同stack命令一样,watch命令也会监听代理方法。

trace

tracestackwatch命令一样,也是监听方法的,它的主要作用是监听方法的调用链路上每个节点的耗时。

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
[E] 开启正则表达式匹配,默认为通配符匹配
[n:] 命令执行次数,默认值为 100。
#cost 方法执行耗时
[m <arg>] 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch <arg>]

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ trace com.itwray.iw.auth.service.impl.AuthUserServiceImpl loginByPassword
Press Q or Ctrl+C to abort.
Affect(class count: 2 , method count: 2) cost in 155 ms, listenerId: 3
`---ts=2024-11-20 14:59:55.762;thread_name=http-nio-18001-exec-2;id=119;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@2f29e630
`---[120.320417ms] com.itwray.iw.auth.service.impl.AuthUserServiceImpl$$SpringCGLIB$$0:loginByPassword()
`---[99.89% 120.190292ms ] org.springframework.cglib.proxy.MethodInterceptor:intercept()
`---[92.07% 110.65475ms ] com.itwray.iw.auth.service.impl.AuthUserServiceImpl:loginByPassword()
+---[0.06% 0.062209ms ] com.itwray.iw.auth.model.dto.LoginPasswordDto:getUsername() #59
+---[4.60% 5.094084ms ] com.itwray.iw.auth.dao.AuthUserDao:queryOneByUsername() #59
+---[0.01% 0.011792ms ] com.itwray.iw.auth.model.dto.LoginPasswordDto:getPassword() #69
+---[0.01% 0.013333ms ] com.itwray.iw.auth.model.entity.AuthUserEntity:getPassword() #69
+---[94.27% 104.315167ms ] cn.hutool.crypto.digest.BCrypt:checkpw() #69
+---[0.01% 0.005542ms ] com.itwray.iw.auth.model.entity.AuthUserEntity:getId() #80
+---[0.61% 0.676208ms ] com.itwray.iw.starter.redis.RedisUtil:set() #80
+---[0.05% 0.056ms ] com.itwray.iw.auth.service.impl.AuthUserServiceImpl:setTokenValue() #83
+---[0.00% 0.003834ms ] com.itwray.iw.auth.model.vo.UserInfoVo:<init>() #86
+---[0.00% 0.003541ms ] com.itwray.iw.auth.model.entity.AuthUserEntity:getName() #87
+---[0.00% 0.003333ms ] com.itwray.iw.auth.model.vo.UserInfoVo:setName() #87
+---[0.00% 0.003042ms ] com.itwray.iw.auth.model.entity.AuthUserEntity:getAvatar() #88
+---[0.00% 0.002833ms ] com.itwray.iw.auth.model.vo.UserInfoVo:setAvatar() #88
+---[0.00% 0.003375ms ] com.itwray.iw.auth.model.vo.UserInfoVo:setTokenName() #89
`---[0.00% 0.002834ms ] com.itwray.iw.auth.model.vo.UserInfoVo:setTokenValue() #90

classloader

作用:将 JVM 中所有的 classloader 的信息统计出来,并可以展示继承树,urls 等。

参数名称 参数说明
[l] 按类加载实例进行统计
[t] 打印所有 ClassLoader 的继承树
[a] 列出所有 ClassLoader 加载的类,请谨慎使用
[c:] ClassLoader 的 hashcode
[classLoaderClass:] 指定执行表达式的 ClassLoader 的 class name
[c: r:] 用 ClassLoader 去查找 resource
[c: load:] 用 ClassLoader 去加载指定的类

直接执行classloader,可以看到所有 classloader 实例数量以及它们各自加载的类数量。

1
2
3
4
5
6
7
8
9
$ classloader
name numberOfInstances loadedCountTotal
jdk.internal.loader.ClassLoaders$AppClassLoader 1 15968
BootstrapClassLoader 1 5228
com.taobao.arthas.agent.ArthasClassloader 1 2063
jdk.internal.loader.ClassLoaders$PlatformClassLoader 1 154
jdk.internal.reflect.DelegatingClassLoader 112 112
sun.reflect.misc.MethodUtil 1 1
Affect(row-cnt:6) cost in 49 ms.

heapdump

作用:生成 dump 文件。类似于 jmap 命令的 heap dump 功能。

dump 到指定文件:heapdump arthas-output/dump.hprof # 注意,这里使用相对路径时,会使用监听的Java进程的运行路径作为根路径。

只 dump live 对象:heapdump --live /tmp/dump.hprof

如果有项目运行环境,可以直接在运行环境执行 jps 找到对应的 pid ,再使用 jcmd pid GC.heap_dump /tmp/dump.hprof 的方式生成 dump 文件。注意 jcmd 是JDK工具,要确保运行环境有安装JDK。

Arthas 实战场景

通过arthas怎样排查项目中,哪个对象泄露了,或者占用内存太大

参考地址:https://arthas.aliyun.com/doc/expert/user-question-history13509.html