一、前言
在本章节中,将学习以下 Arthas 的 JVM 相关命令,同时我也会附上官方文档的链接,方便大家查阅:
- dashboard 仪表板,可以显示:线程,内存,堆栈,GC,Runtime 等信息
- thread 显示线程的堆栈
- jvm 显示 JAVA 虚拟机信息
- sysprop 显示 JVM 中系统属性,也可以修改某个属性
- sysenv 显示 JVM 中系统环境变量配置信息
- vmoption 显示 JVM 中选项信息
- getstatic 获取类中静态成员变量
- ognl 执行一条 ognl 表达式,对象图导航语言
二、dashboard
显示当前系统的实时数据面板,按 q
或 ctrl+c
退出。
2.1 数据说明
列名 | 描述 |
---|---|
ID | Java 级别的线程ID,注意这个 ID 不能跟 jstack 中的 nativeID 一一对应 |
NAME | 线程名 |
GROUP | 线程组名 |
PRIORITY | 线程优先级, 1~10之间的数字,越大表示优先级越高 |
STATE | 线程的状态 |
%CPU | 线程消耗的 CPU 占比,采样 100ms,将所有线程在这 100ms 内的 CPU 使用量求和,再算出每个线程的 CPU 使用占比。 |
DELTA_TIME | 上次采样之后线程运行增量CPU时间,数据格式为秒 |
TIME | 线程运行总时间,数据格式为分:秒 |
INTERRUPTED | 线程当前的中断位状态 |
DAEMON | 是否是守护线程 |
2.2 内部线程
在使用 dastboard
或者 thread
命令时,会看到有些线程的 ID 为 -1。这是在 Java 8 之后支持观测 JVM 的内部线程。
这些内部线程只有名称和 CPU 时间,没有 ID 及状态等信息。 通过内部线程可以观测到 JVM 活动,如 GC、JIT 编译等占用 CPU 情况,方便了解 JVM 整体运行状况。
JVM内部线程包括下面几种:
- JIT 编译线程:
C1 CompilerThread0
,C2 CompilerThread0
等 - GC 线程:
GC Thread0
,G1 Young RemSet Sampling
等 - 其它内部线程:
VM Periodic Task Thread
,VM Thread
,Service Thread
等
当 JVM 堆(heap)/元数据(metaspace)空间不足或 OOM 时,可以看到 GC 线程的 CPU 占用率明显高于其他的线程。
当执行 trace/watch/tt/redefine
等命令后,可以看到 JIT 线程活动变得更频繁。因为 JVM 热更新 class 字节码时清除了此 class 相关的 JIT 编译结果,需要重新编译。
三、thread
参数名称 | 参数说明 |
---|---|
id | 线程 ID |
[n:] | 指定最忙的前 N 个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i <value> ] |
指定 CPU 使用率统计的采样间隔,单位为毫秒,默认值为 200 |
[–all] | 显示所有匹配的线程 |
3.1 CPU 使用率
这里的 CPU 使用率与 Linux 命令 top -H -p <pid>
的线程 %CPU
类似,一段采样间隔时间内,当前 JVM 里各个线程的增量 CPU 时间与采样间隔时间的比例。
工作原理如下:
(1)首先第一次采样,获取所有线程的CPU时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime()
及sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()
接口)
(2)然后睡眠等待一个间隔时间(默认为 200ms,可以通过 -i
指定间隔时间)
(3)再次第二次采样,获取所有线程的 CPU 时间,对比两次采样数据,计算出每个线程的增量CPU 时间
(4)计算得到 CPU 使用率
线程 CPU 使用率 = 线程增量 CPU 时间 / 采样间隔时间 * 100%
注意: 这个统计也会产生一定的开销(JDK 这个接口本身开销比较大),因此会看到 as 的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如 5000 毫秒。
3.2 展示当前最忙的 N 个线程
1 | thread -n 3 |
-
如果没有进程 ID,且包含
[Internal]
表示这是 JVM 内部线程,和 dashboard 命令中相同。 -
cpuUsage
为采样间隔时间内线程的 CPU 使用率,和 dashboard 命令中相同。 -
deltaTime
为采样间隔时间内线程的增量 CPU 时间,小于 1ms 时被取整显示为 0ms。 -
time
线程运行总CPU时间。
3.3 显示第一页线程
默认按照 CPU 增量时间降序排列,只显示第一页数据。
1 | thread |
3.4 显示所有线程
有时需要获取全部JVM的线程数据进行分析,可以一次全部展示。
1 | thread -all |
3.5 显示单个线程运行堆栈
1 | thread <pid> |
3.6 找出当前阻塞其他线程的线程
有时候我们发现应用卡住了, 通常是由于某个线程拿住了某个锁, 并且其他线程都在等待这把锁造成的(即死锁),使用该命令可以一键找出。
1 | thread -b |
注意:3.4.5 版本目前只支持找出synchronized关键字阻塞住的线程, 如果是
java.util.concurrent.Lock
, 目前还不支持。
3.7 指定采样间隔
thread -i 1000
: 统计最近 1000ms 内的线程 CPU 时间thread -n 3 -i 1000
: 列出 1000ms 内最忙的 3 个线程栈
3.8 指定状态所有线程
1 | thread –state <state> |
四、JVM
查看当前 JVM 的信息
4.1 Thread 相关
字段 | 含义 |
---|---|
COUNT | JVM 当前活跃的线程数 |
DAEMON-COUNT | JVM 当前活跃的守护线程数 |
PEAK-COUNT | 从 JVM 启动开始曾经活着的最大线程数 |
STARTED-COUNT | 从 JVM 启动开始总共启动过的线程次数 |
DEADLOCK-COUNT | JVM 当前死锁的线程数 |
4.2 文件描述符相关
字段 | 含义 |
---|---|
MAX-FILE-DESCRIPTOR-COUNT | JVM 进程最大可以打开的文件描述符数 |
OPEN-FILE-DESCRIPTOR-COUNT | JVM 当前打开的文件描述符数 |
五、sysprop
即 System Property,查看和修改 JVM 的系统属性。
(1)查看所有属性
1 | sysprop |
(2)查看单个属性(支持自动补全)
1 | sysprop <key> |
(3)修改单个属性
1 | sysprop <key> <value> |
六、sysenv
即 System Environment Variables,查看当前 JVM 的环境属性。
(1)查看所有环境变量
1 | sysenv |
(2)查看单个环境变量(支持自动补全)
1 | sysenv <key> |
七、vmoption
查看、更新 VM 诊断相关的参数。
(1)查看所有选项
1 | vmoption |
(2)查看单个选项(支持自动补全)
1 | vmoption <key> |
3)修改单个选项
1 | vmoption <key> <value> |
八、getstatic
推荐直接使用 ognl 命令,更加灵活
通过 getstatic 命令可以方便的查看类的静态属性。
1 | getstatic <class_name> <field_name> |
九、ognl
自 3.0.5 起,Arthas 支持执行 OGNL 表达式。OGNL 的语法需要我们额外学习,点击这里查看详细文档。
参数名称 | 参数说明 |
---|---|
express | 执行的表达式 |
[c:] |
执行表达式的 ClassLoader 的 hashcode,默认值是SystemClassLoader |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[x] | 结果对象的展开层次,默认值1 |
(1)调用静态函数
1 | ognl '@java.lang.System@out.println("hello")' |
(2)调用静态函数
1 | ognl '@demo.MathGame@random' |
(3)执行多行表达式,赋值给临时变量,返回一个List
1 | ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}' |