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
来获取更多参数信息。
当前系统的实时数据面板,按 ctrl+c 退出。
当运行在Ali-tomcat时,会显示当前tomcat的实时信息,如HTTP请求的qps, rt, 错误数, 线程池信息等等。
参数名称 | 参数说明 |
---|---|
[i:] | 刷新实时数据的时间间隔 (ms),默认5000ms |
[n:] | 刷新实时数据的次数 |
查看当前线程信息,查看线程的堆栈
参数名称 | 参数说明 |
---|---|
id | 线程id |
[n:] | 指定最忙的前N个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i value ] |
指定cpu使用率统计的采样间隔,单位为毫秒,默认值为200 |
[--all] | 显示所有匹配的线程 |
这里的cpu
使用率与linux
命令top -H -p <pid>
的线程%CPU
类似,一段采样间隔时间内,当前JVM
里各个线程的增量cpu
时间与采样间隔时间的比例。
java.lang.management.ThreadMXBean#getThreadCpuTime()
及sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()
接口)-i
指定间隔时间)注意: 这个统计也会产生一定的开销(JDK这个接口本身开销比较大),因此会看到as的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如5000毫秒。
另外一种查看Java进程的线程cpu使用率方法:可以使用show-busy-java-threads这个脚本
thread -n 3
[Internal]
表示为JVM内部线程 。cpuUsage
为采样间隔时间内线程的CPU使用率,与[dashboard]命令的数据一致。deltaTime
为采样间隔时间内线程的增量CPU时间,小于1ms时被取整显示为0ms。time
线程运行总CPU时间。注意:线程栈为第二采样结束时获取,不能表明采样间隔时间内该线程都是在处理相同的任务。建议间隔时间不要太长,可能间隔时间越大越不准确。 可以根据具体情况尝试指定不同的间隔时间,观察输出结果。
默认按照CPU增量时间降序排列,只显示第一页数据。
显示所有匹配线程信息,有时需要获取全部JVM的线程数据进行分析。
thread 1
有时候我们发现应用卡住了, 通常是由于某个线程拿住了某个锁, 并且其他线程都在等待这把锁造成的。 为了排查这类问题, arthas提供了thread -b
, 一键找出那个罪魁祸首。
thread b
注意, 目前只支持找出synchronized关键字阻塞住的线程, 如果是
java.util.concurrent.Lock
, 目前还不支持。
thread -i 1000
: 统计最近1000ms内的线程CPU时间。thread -n 3 -i 1000
: 列出1000ms内最忙的3个线程栈thread --state WAITING
查看当前JVM信息
查看当前JVM的系统属性(
System Property
)
查看当前JVM的环境属性(
System Environment Variables
)
查看,更新VM诊断相关的参数
查看当前JVM的 Perf Counter信息
查看logger信息,更新logger level
反编译指定已加载类的源码
jad
命令将 JVM 中实际运行的 class 的 byte code 反编译成 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
都加载了这个类时,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>
是动态变化的。
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 命令实现热更新代码。
加载外部的
.class
文件,retransform 热更新jvm已加载的类。
retransform /tmp/Test.class retransform -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
查看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
利用JVMTI
接口,实现查询内存对象,强制GC等功能。
输出当前方法被调用的调用路径
很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 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
命令能主动搜索 class-pattern
/method-pattern
对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] |
命令执行次数 |
#cost |
方法执行耗时 |
这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}"
,只要是一个合法的 ognl 表达式,都能被正常支持。
观察的维度也比较多,主要体现在参数 advice
的数据结构上。Advice
参数最主要是封装了通知节点的所有信息。
方法执行数据观测
让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值
、抛出异常
、入参
,通过编写 OGNL 表达式进行对应变量的查看。
方法执行监控
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
watch
虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。
这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。
于是乎,TimeTunnel 命令就诞生了。
打印文件内容,和linux里的cat命令类似。
类似传统的
grep
命令。