一、简介

Arthas是Alibaba开源的一款Java诊断工具,采用命令式交互模式,用来排查各种JVM的问题。

Arthas主要提供了一下几种功能

1、实时监控JVM运行状况

2、实时查看已加载的类型和类加载器信息

3、通过字节码增强技术实现方法执行的监控和统计

二、Arthas的使用

2.1、Arthas安装启动

Arthas本质上也是一个Java程序,没有安装流程,只需要下载jar包,通过java命令直接启动即可

下载arthas-boot.jar,然后用java -jar的方式启动:

下载命令:

wget https://arthas.aliyun.com/arthas-boot.jar

 

启动命令:

java -jar arthas-boot.jar

 

启动之后会列出当前节点正在运行的所有Java进程,并且有对应的序号,可以输入序号来选择需要访问的进程信息,如选择第一个进程就直接输入1即可,启动效果如下图示:

阿里开源Java诊断工具--Arthas入门_链路

 

表示Attach上进程号为21093的Java进程,并且此时就进入Arthas的命令交互模式,不可以再输入操作系统的命令,只可以输入Arthas相关的命令,可以输入help命令查看所有Arthas支持的命令,如下:

阿里开源Java诊断工具--Arthas入门_数据_02

 

2.2、Arthas命令概览

Arthas命令主要分成几个类型,分别是基本命令、JVM相关命令、class相关命令、监控相关命令

2.2.1、基本命令

命令  用途
help 查看命令帮助信息
cat 打印文件内容
echo 打印参数
grep 匹配查找
base64 base64编码转换
tee 复制标志输入到标准输出和指定的文件
pwd 返回当前工作目录
cls 清空屏幕内容
session 查看当前会话信息
reset 重置增强类,将被Arthas增强过的类全部还原,Arthas服务关闭时自动还原
version 输出当前目标Java进程加载的Arthas版本号
history 打印命令历史
quit 退出当前Arthus客户端
stop 关闭Arthas服务端,所有Arthas客户端全部退出

2.2.2、JVM相关命令

命令 用途 用法
dashboard 打印当前JVM实时数据面板 dashboard
thread 打印当前JVM线程堆栈信息

thread

thread 线程ID

thread --state WAITING 

jvm

打印当前JVM实时运行信息 jvm
sysprop 打印和修改当前JVM信息 sysprop
sysenv  查看当前JVM环境变量 sysenv
vmoption 查看和修改JVM里诊断相关option vmoption
logger 查看和修改logger信息 logger
getstatic 获取静态变量的值 getstatic [className] [staticField]
ognl 执行ognl表达式 ognl 表达式
mbean 打印MBean信息 mbean
heapdump 打印堆栈信息

heapdump

heapdump /test/dump/test.hprof

heapdump --live /test/dump/test.hprof

vmtool 从jvm里查询对象,执行forceGc vmtool --action getInstances --className [className]
perfcounter 查看当前JVM的 Perf Counter信息 perfcounter

 

2.2.3、class相关命令

命令 用途 用法
 sc  查询JVM已加载的类信息

 sc  *[className]*

 sc -d *[className]*

sc -d -f *[className]*

 sm 查询JVM已加载的方法信息 

 sm [className]

sm -f [className]

 jad 反编译JVM已加载的类信息   jad [className]
 mc  内存编译器,通过内存编译.java文件

 mc /com/test/test.java

mc -c [类加载ID] /com/test/test.java

mc --classLoadClass [classLoadClassName] /com/test/test.java

mc -d /tmp/file /com/test/test.java 

 retransform  加载外部.class文件,retransform到JVM中  retransform /com/test/test.class
 redefine  加载外部.class文件,redefine到JVM中 redefine /com/test/test/class 
 dump  dump已加载类的字节码到指定目录

 dump java.lang.String

dump -d /tmp/file java.lang.String

 classloader 查看类加载器继承树信息 

 classloader

classloader -l

classloader -t

classloader -c [类加载ID] --load [className]

 

2.2.4、监控相关命令

监控相关的命令是通过字节码增强技术将增强的逻辑织入到目标类中,所以监控完成之后需求及时执行reset命令去除增强的逻辑

命令 用途 用法
watch 方法执行数据观测 watch [className] [methodName] "{params,returnObj}" -x 2
monitor 方法执行监控 monitor -c 5 [className] [methodName]
stack 输出当前方法被调用的路径 stack [className] [methodName]
trace 方法内部的调用路径,并打印路径耗时 trace [className] [methodName]
tt 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 tt -t [className] [methodName]

 

2.3、Arthas命令详解

2.3.1、dashborad(实时看板) 

语法:dashboard [i:] [n:]

dashboard  ##默认每个5秒打印一次JVM实时数据
dashboard -i 2000  ##每隔2秒打印一次JVM实时数据
dashboard -n 10  ##总共打印10次JVM实时数据
dashboard -i 1000 -n 10 ##每隔1秒打印一次JVM实时数据,共打印10次

dashboard命令用于实时查看JVM运行信息,包括三个模块分布是线程运行情况、内存使用情况以及JVM运行环境信息等

阿里开源Java诊断工具--Arthas入门_数据_03

 

线程模块会打印当前JVM所有的线程运行状况,包括线程ID、名称、优先级、状态、占有CPU比率、占有CPU时长等;

内存模块会打印JVM当前堆内和堆外内存使用情况以及GC的次数和时间统计

运行环境模块会打印当前操作系统和JDK的版本信息以及运行环境等信息

2.3.2、thread(线程信息)

通过thread命令可以打印当前所有运行的线程信息,并且可以通过thread 线程ID的方式查看指定线程的栈信息

语法

thread 查询所有线程

thread <id>  查询指定线程

thread -n <n>  查询最忙的n个线程

thread -i <i> : 统计最近指定毫秒内的线程CPU时间

thread -n <n> -i <i> : 列出最近指定毫秒内最忙的N个线程栈

thread -b 查询阻塞其他线程的线程

thread --state [RUNNABEL]|[WAITING]|[TIMED_WAITING]|[NEW]|[BLOCKED]|[TERMINATED]  查询指定状态的线程

thread   ## 查看所有线程信息
thread <id> ## 查看指定线程ID的信息
thread -n 10 ## 查看最忙的10个线程
thread -b ## 查看阻塞其他线程的线程信息
thread --state WAITING ## 查看等待状态的线程信息

 

阿里开源Java诊断工具--Arthas入门_反编译_04

阿里开源Java诊断工具--Arthas入门_反编译_05

通过thread查看所有线程信息,除了业务线程还会包含JVM内部线程,JVM内部线程ID为-1,包括GC线程如GC task thread#2 (ParallelGC)、JIT编译线程如C2 CompilerThread0、其他内部线程如VM Periodic Task Thread等

thread -b 命令可以找出当前阻塞了其他线程的线程,比如线程A通过Synchronized关键字拿到了锁,线程B和线程C被阻塞了,那么此时就可以通过thread -b命令查询出来,目前也仅支持Synchronized获取锁阻塞的情况

2.3.3、jvm(查看JVM信息)

用法:jvm

jvm命令可以打印当前JVM的实时信息,包括运行环境、加载的类统计、内存使用情况、GC统计情况、线程统计情况、文件描述符统计信息等

THREAD相关

  • COUNT: JVM当前活跃的线程数

  • DAEMON-COUNT: JVM当前活跃的守护线程数

  • PEAK-COUNT: 从JVM启动开始曾经活着的最大线程数

  • STARTED-COUNT: 从JVM启动开始总共启动过的线程次数

  • DEADLOCK-COUNT: JVM当前死锁的线程数

文件描述符相关

  • MAX-FILE-DESCRIPTOR-COUNT:JVM进程最大可以打开的文件描述符数

  • OPEN-FILE-DESCRIPTOR-COUNT:JVM当前打开的文件描述符数

2.3.4、jad(反编译)

语法

jad <类完整路径>  反编译指定类

jad <类完整路径> <方法名> 反编译指定方法

通过sc可以查看所有已加载的类信息,然后就可以通过jad命令可以将已加载的类.class文件进行反编译,命令如下:

jad com.test.ArthasDemo

2.3.5、sc(查看已加载的类)

语法

sc [-d] [-f] *<className>*  

[-d] 表示打印详细信息;[-f]表示打印属性信息,可以通过关键字模糊查询

sc是search class的缩写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,这个命令支持的参数有 [d][E][f] 和 [x:],并且支持模糊查询

如想搜索后缀为Service的类,则可以输入一下命令:

sc -d -f *Service

-d表示输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。如果一个类被多个ClassLoader所加载,则会出现多次

-f表示输出当前类的成员变量信息(需要配合参数-d一起使用)

通过该命令可以查看已加载的类的详细信息,包括code-source表示从哪个包中加载的,使用哪个class-loader类加载器加载的等,案例如下图示:

阿里开源Java诊断工具--Arthas入门_java_06

2.3.6、sm(查看已加载类的方法)

用法

sm <类完整路径>

sm -d <类完整路径>

sm -d <类完整路径> <方法名>

sm命令可以查看已加载的类的函数,比如查看java.math.RoundingMode类的所有方法,则命令如下:

sm -d java.math.RoundingMode

阿里开源Java诊断工具--Arthas入门_数据_07

2.3.7、getstatic(查看静态属性)

用法

getstatic <类完整路径> <静态属性名>

通过getstatic命令可以查看已加载的类的静态属性的值

阿里开源Java诊断工具--Arthas入门_加载_08

2.3.8、watch(监控方法执行数据)

用法

watch <className> <method> 观察指定类指定方法的执行情况,返回耗时,返回值等信息

watch <className> <method> "{params,returnObj}"-x <x> 观察入参和出参,返回结果便利深度为x,默认深度为1

watch <className> <method> "{params,returnObj}"-b 观测方法调用前的入参和返回值,调用方法前返回值肯定为空

watch <className> <method> "{params,returnObj}" -b -s -n <n> 同时观察方法执行前和方法执行后的入参和返回值,监控n次

watch <className> <method> "{params,throwExp}" -e 观察方法在抛出异常时的入参和异常信息

watch <className> <method> "{params,target}" -b -s 观察方法执行前和方法执行后入参和当前对象的信息

watch <className> <method> "{params,target.fieldName}" -b -s 观察方法执行前和方法执行后入参和当前对象的fieldName属性的值

watch <className> <method> "{params}" '#cost>100' -f 观察方法执行后执行耗时大于100毫秒的入参信息

通过watch命令可以监控指定类的指定方法的参数、返回值和异常信息,watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后,

4个观察事件点 -b-e-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出

这里要注意方法入参方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参

当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在

2.3.9、monitor(方法执行监控)

用法

monitor -c <second> <className> <methodName> 定时second秒统计一次指定类指定方法的调用情况

monitor -c <second> -b <className> <methodName> 'params[1] > 1' 方法调用之前统计第二个参数大于1的调用情况

对匹配 class-patternmethod-patterncondition-express的类、方法的调用进行监控。

monitor 命令是一个非实时返回命令.

实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。

服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何Arthas命令不会引起原有业务逻辑的改变。

monitor和watch的区别是watch是实时监控,每次方法调用都会打印;monitor是统计之后定时打印,watch倾向于实时查看,monitor倾向于定时统计

monitor可以监控的维度包括:timestamp(时间戳)、class(类)、method(方法)、total(调用次数)、success(成功次数)、fail(失败次数)、rt(平均RT)、fail-rate(失败率)

monitor结果如下图示:

阿里开源Java诊断工具--Arthas入门_反编译_09

 

2.3.10、trace(方法调用路径)

用法

trace <className> <methodName> 实时打印指定类指定方法的调用链路

trace <className> <methodName> --skipJDKMethod false 打印JDK方法执行链路,默认会被过滤

trace <className> <method> '#cost > 100' 过滤耗时大于100毫秒的调用链路

trace <className> <method> '#cost > 100' -n <n> 限制实时监控的次数为n次

trace可以查询指定类的指定方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路

阿里开源Java诊断工具--Arthas入门_数据_10

 打印信息中包含了整个链路的每一步,并且打印了每一个链路的代码行数和耗时情况

 

2.3.11、stack(输出当前方法被调用的路径)

用法

stack <className> <methodName> 打印某个方法被调用的链路

stack <className> <methodName> 'params[0] > 1' 按入参条件过滤

stack <className> <methodName> '#cost > 100' 按耗时进行过滤

当一个方法可以被很多地方调用时,想要查询当前方法是被哪个方法调用,此时就可以通过stack命令得知当前方法是从什么地方被执行的,如下图示

 

阿里开源Java诊断工具--Arthas入门_java_11 

3、Arthas使用案例

案例一:排查方法执行异常

场景:线上某个接口偶尔会报500错误,此时就需要排查报500错误时的具体参数

可以通过watch命令监控接口报500错误时的参数和异常信息命令如下:

watch <类路径,支持通配符> <方法名,支持通配符> "{params, throwExp}" -e 表示当发生异常时打印出参数和异常信息内容

阿里开源Java诊断工具--Arthas入门_链路_12

 

 


 

案例二:热更新代码

场景:线上存在一个小BUG,需要修改部分代码就可以修复,而发版成本较大时就可以通过热更新的方式替换线上的代码逻辑

热更新涉及到几个步骤,

1、首先需要将.class文件进行反编译成源代码文件

2、然后通过编辑vim编辑源代码文件更新代码逻辑

3、然后再将新源代码文件编译成.class文件

4、最后再将新的.class文件替换掉JVM中的已加载的.class文件

假设用一个UserController的getUserDetail代码如下:

1 @RestController
2 @RequestMapping(value = "/testUser")
3 public class TestController {
4 
5     @RequestMapping(value = "/detail", method = RequestMethod.GET)
6     public String getUserName(@RequestParam("userId") String userId){
7         return "name is :" + Long.valueOf(userId);
8     }
9 }

 

 

 

由于参数userId定义的是String类型,因此可能会出现第7行的转换异常,所以需要将userId类型改成Long类型,并通过热更新的方式部署

第一步:通过jad命令反编译UserController类,并将反编译后的代码写入临时文件夹

 jad --source-only com.zjic.message.business.controller.TestController > /tmp/TestController.java

 

第二步:通过vim来编辑TestController.java文件,修改源代码将入参的String类型改成Long类型

vim /tmp/TestController

 

第三步:编译新的TestController.java文件

编译UserController可以指定原先的类加载器编译,那么可以先通过sc命令找到原先的类加载

sc -d *TestController | grep 'classLoader'

 

 

 

阿里开源Java诊断工具--Arthas入门_链路_13

查到classLoader的hash码后就可以通过mc命令编译新的TestController.java文件,并写入到tmp目录生成TestController.class文件

 mc -c 17d99928 /tmp/TestController.java -d /tmp

阿里开源Java诊断工具--Arthas入门_加载_14

 

第四步:通过redefine命令将新生成的TestController.class替换掉JVM中已加载的TestController

1 redefine /tmp/com/zjic/message/business/controller/TestController.class

 

结果如下:

阿里开源Java诊断工具--Arthas入门_加载_15

 

提示删除了一个方法所以替换失败,因为通过反编译修改新的TestController.java文件时将方法的参数类型进行了修改,那么就相当于重新定义了一个方法,所以想要通过redefine替换.class文件时,类中的方法个数、参数个数、参数类型以及返回值都不允许修改,只可以修改方法内部的实现逻辑。

重新修改TestController.java文件,不修改参数userId的类型在方法体内部添加try/catch捕获异常,

       @RestController
       @RequestMapping(value={"/testUser"})
       public class TestController {
           @RequestMapping(value={"/detail"}, method={RequestMethod.GET})
           public String getUserName(@RequestParam(value="userId") String userId)        { 
            Long id = null;
            try{
                id = Long.parseLong(userId);
              }catch(Exception e){ }
               return "name is :" + id;
           }
       }

 

并重新编译TestController.class文件,最后重新执行redefine命令,结果如下:

阿里开源Java诊断工具--Arthas入门_链路_16

 

测试接口验证通过,从而成功实现了热更新功能


案例三:线上慢请求排查

场景:当访问线上某个接口时,发现接口相应比较慢并且通过代码又不太好排查出具体是哪一行代码引起的慢请求

通过trace命令可以监控接口方法的调用链路和各个链路的耗时,从而可以分析接口最耗时的代码块在哪,案例如下:

 

trace com.zjic.message.business.controller.BusinessApplyController * '#cost > 1'

 

 

阿里开源Java诊断工具--Arthas入门_反编译_17

 

先监控指定Controller的所有方法,采用*通配符匹配,然后可以发现最耗时的代码是Service层的方法,此时就可以再继续使用trace命令继续查看Service层方法的调用链路

阿里开源Java诊断工具--Arthas入门_链路_18

 

最终定位到最耗时的是Mapper层的方法,所以该接口最耗时的就是SQL语句的执行,那么就可以针对SQL优化来进行接口的优化