系统稳定性—Java诊断工具Arthas使用

ArthasAlibaba开源的Java诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个jar包加载的?为什么会报各种类相关的Exception
  2. 我改的代码为什么没有执行到?难道是我没commit?分支搞错了?
  3. 遇到问题无法在线上debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?
  7. 怎么快速定位应用的热点,生成火焰图?
  8. 怎样直接从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请求的qpsrt,错误数,线程池信息等等。

参数说明

参数名称 参数说明
[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中实际运行的classbyte 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-patternmethod-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命令。

posted @ 2022-04-24 11:25  夏尔_717  阅读(458)  评论(0编辑  收藏  举报