系统稳定性—Java诊断工具Arthas使用
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
打印帮助信息:
java -jar arthas-boot.jar -h
- 如果下载速度比较慢,可以使用
aliyun
的镜像:java -jar arthas-boot.jar --repo-mirror aliyun --use-http
使用as.sh
Arthas
支持在Linux/Unix/Mac
等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲回车
执行即可:
curl -L https://arthas.aliyun.com/install.sh | sh
上述命令会下载启动脚本文件as.sh
到当前目录,你可以放在任何地方或将其加入到$PATH
中。
直接在shell
下面执行./as.sh
,就会进入交互界面。
也可以执行./as.sh -h
来获取更多参数信息。
案例展示
Dashboard
当前系统的实时数据面板,按
ctrl+c
退出。
当运行在Ali-tomcat
时,会显示当前tomcat
的实时信息,如HTTP
请求的qps
,rt
,错误数,线程池信息等等。
参数说明
参数名称 | 参数说明 |
---|---|
[i:] | 刷新实时数据的时间间隔 (ms),默认5000ms |
[n:] | 刷新实时数据的次数 |
Thread
查看当前线程信息,查看线程的堆栈
参数说明
参数名称 | 参数说明 |
---|---|
id | 线程id |
[n:] | 指定最忙的前N个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i value ] |
指定cpu使用率统计的采样间隔,单位为毫秒,默认值为200 |
[--all] | 显示所有匹配的线程 |
cpu使用率是如何统计出来的?
这里的cpu
使用率与linux
命令top -H -p <pid>
的线程%CPU
类似,一段采样间隔时间内,当前JVM
里各个线程的增量cpu
时间与采样间隔时间的比例。
工作原理说明:
- 首先第一次采样,获取所有线程的
CPU
时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime()
及sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()
接口) - 然后睡眠等待一个间隔时间(默认为
200ms
,可以通过-i
指定间隔时间) - 再次第二次采样,获取所有线程的
CPU
时间,对比两次采样数据,计算出每个线程的增量CPU
时间 - 线程
CPU
使用率 = 线程增量CPU
时间 / 采样间隔时间 * 100%
注意:这个统计也会产生一定的开销(
JDK
这个接口本身开销比较大),因此会看到as
的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如5000
毫秒。
另外一种查看
Java
进程的线程cpu
使用率方法:可以使用show-busy-java-threads这个脚本
使用参考
支持一键展示当前最忙的前N个线程并打印堆栈:thread -n 3
- 没有线程ID,包含
[Internal]
表示为JVM内部线程。 cpuUsage
为采样间隔时间内线程的CPU使用率,与[dashboard]命令的数据一致。deltaTime
为采样间隔时间内线程的增量CPU时间,小于1ms时被取整显示为0ms。time
线程运行总CPU时间。
注意:线程栈为第二采样结束时获取,不能表明采样间隔时间内该线程都是在处理相同的任务。建议间隔时间不要太长,可能间隔时间越大越不准确。可以根据具体情况尝试指定不同的间隔时间,观察输出结果。
当没有参数时,显示第一页线程的信息
默认按照CPU增量时间降序排列,只显示第一页数据。
thread –all, 显示所有匹配的线程
显示所有匹配线程信息,有时需要获取全部JVM的线程数据进行分析。
thread id,显示指定线程的运行堆栈
thread 1
thread -b,找出当前阻塞其他线程的线程
有时候我们发现应用卡住了,通常是由于某个线程拿住了某个锁,并且其他线程都在等待这把锁造成的。为了排查这类问题,arthas
提供了thread -b
,一键找出那个罪魁祸首。
thread b
注意,目前只支持找出synchronized关键字阻塞住的线程,如果是
java.util.concurrent.Lock
,目前还不支持。
thread -i,指定采样时间间隔
thread -i 1000
:统计最近1000ms内的线程CPU时间。thread -n 3 -i 1000
:列出1000ms内最忙的3个线程栈
thread –state,查看指定状态的线程
thread --state WAITING
jvm
查看当前JVM信息
sysprop
查看当前JVM的系统属性(
System Property
)
sysenv
查看当前JVM的环境属性(
System Environment Variables
)
vmoption
查看,更新VM诊断相关的参数
perfcounter
查看当前JVM的 Perf Counter信息
logger
查看logger信息,更新logger level
jad
反编译指定已加载类的源码
jad
命令将JVM
中实际运行的class
的byte code
反编译成java
代码,便于你理解业务逻辑;
- 在
Arthas Console
上,反编译出来的源码是带语法高亮的,阅读更方便 - 当然,反编译出来的
java
代码可能会存在语法错误,但不影响你进行阅读理解
参数说明
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
[c:] |
类所属 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
使用参考
反编译java.lang.String
jad java.lang.String
反编译时只显示源代码
默认情况下,反编译结果里会带有ClassLoader
信息,通过--source-only
选项,可以只打印源代码。方便和mc /retransform
命令结合使用。
jad --source-only demo.MathGame
反编译指定的函数
jad demo.MathGame main
反编译时不显示行号
--lineNumber
参数默认值为true,显示指定为false则不打印行号。
jad demo.MathGame main --lineNumber false
反编译时指定ClassLoader
当有多个
ClassLoader
都加载了这个类时,jad
命令会输出对应ClassLoader</span>
实例的<span class="pre">hashcode
,然后你只需要重新执行jad
命令,并使用参数-c <hashcode>
就可以反编译指定ClassLoader
加载的那个类了;
jad org.apache.log4j.Logger
对于只有唯一实例的ClassLoader
还可以通过--classLoaderClass
指定class name
,使用起来更加方便:
--classLoaderClass
的值是ClassLoader的类名,只有匹配到唯一的ClassLoader实例时才能工作,目的是方便输入通用命令,而-c <hashcode>
是动态变化的。
mc
Memory Compiler/内存编译器,编译
.java
文件生成.class
。
mc /tmp/Test.java
可以通过-c
参数指定classloader:
mc -c 327a647b /tmp/Test.java
也可以通过<span class="pre">--classLoaderClass</span>
参数指定ClassLoader:
mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader
可以通过-d
命令指定输出目录:
mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java
编译生成.class
文件之后,可以结合retransform
命令实现热更新代码。
retransform
加载外部的
.class
文件,retransform热更新
jvm`已加载的类。
retransform /tmp/Test.class
retransform -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
sc
查看JVM已加载的类信息
“Search-Class”的简写,这个命令能搜索出所有已经加载到JVM
中的Class
信息,这个命令支持的参数有[d]
、[E]
、[f]
和[x:]
。
参数说明
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。如果一个类被多个ClassLoader所加载,则会出现多次 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[f] | 输出当前类的成员变量信息(需要配合参数-d一起使用) |
[x:] | 指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出 |
[c:] |
指定class的 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定执行表达式的 ClassLoader 的 class name |
[n:] |
具有详细信息的匹配类的最大数量(默认为100) |
vmtool
vmtool
利用JVMTI
接口,实现查询内存对象,强制GC等功能。
stack
输出当前方法被调用的调用路径
很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是stack
命令。
参数说明
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] |
执行次数限制 |
这里重点要说明的是观察表达式,观察表达式的构成主要由ognl
表达式组成,所以你可以这样写"{params,returnObj}"
,只要是一个合法的ognl
表达式,都能被正常支持。
观察的维度也比较多,主要体现在参数advice
的数据结构上。Advice
参数最主要是封装了通知节点的所有信息。
使用例子
stack demo.MathGame primeFactors
据条件表达式来过滤
stack demo.MathGame primeFactors 'params[0]<0' -n 2
据执行时间来过滤
stack demo.MathGame primeFactors '#cost>5'
Trace
方法内部调用路径,并输出方法路径上的每个节点上耗时
trace
命令能主动搜索class-pattern
/method-pattern
对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
参数说明
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] |
命令执行次数 |
#cost |
方法执行耗时 |
这里重点要说明的是观察表达式,观察表达式的构成主要由ognl
表达式组成,所以你可以这样写"{params,returnObj}"
,只要是一个合法的ognl
表达式,都能被正常支持。
观察的维度也比较多,主要体现在参数advice
的数据结构上。Advice
参数最主要是封装了通知节点的所有信息。
Watch
方法执行数据观测
让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值
、抛出异常
、入参
,通过编写OGNL
表达式进行对应变量的查看。
Monitor
方法执行监控
Time Tunnel(tt) (待完善)
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
watch
虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。
这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。
于是乎,TimeTunnel
命令就诞生了。
cat
打印文件内容,和linux里的cat命令类似。
grep
类似传统的
grep
命令。