Alibaba开源的Java诊断工具

1.支持管道操作;2.-h查看帮助;3.tab自动补全;4.keymap查看快捷键;5.方向键上和下查看历史命令,或通过history命令

1.启动需要监测的应用程序

2.启动arthas,attach到自己的程序上

1
2
3
wget https://alibaba.github.io/arthas/arthas-boot.jar
#阿里云镜像地址
java -jar arthas-boot.jar --repo-mirror aliyun --use-http

3.输入序号,回车。attach成功后,会打印Logo


常用命令

dashboard:查看当前系统的实时数据面板

thread:打印线程栈信息thread [id]

jad:反编译代码jad demo.MathGame

watch:查看函数的参数/返回值/异常信息watch demo.MathGame primeFactors returnObj

sc:查找JVM里已加载的类sc -d *MathGame

exitquit:退出当前session

shutdown:停止程序

resert:清除增强代码(Arthas在 watch/trace 等命令时,实际上是修改了应用的字节码,插入增强的代码。)

参数

--target-ip:允许外部访问

--versions:列出所有版本

--use-version 3.1.0:使用指定版本

telnet-port 9999 --http-port -1:只监听Telnet端口,不监听HTTP端口

-v:打印运行详情

查看JVM信息

sysprop

打印所有的System Properties信息

也可以指定单个key: sysprop java.version

也可以通过grep来过滤: sysprop | grep user

可以设置新的value: sysprop testKey testValue

sysenv

sysenv 命令可以获取到环境变量。和sysprop命令类似。

jvm

jvm 命令会打印出JVM的各种详细信息。

dashboard

dashboard 命令可以查看当前系统的实时数据面板。

输入 Q 或者 Ctrl+C 可以退出dashboard命令。

sc/sm 查看已加载的类

sc

sc 命令可以查找到所有JVM已经加载到的类。

如果搜索的是接口,还会搜索所有的实现类。比如查看所有的Filter实现类:

1
sc javax.servlet.Filter

通过-d参数,可以打印出类加载的具体信息,很方便查找类加载问题。

1
sc -d javax.servlet.Filter

sc支持通配,比如搜索所有的StringUtils

1
sc *StringUtils

sm

sm命令则是查找类的具体函数。比如:

1
sm java.math.RoundingMode

通过-d参数可以打印函数的具体属性:

1
sm -d java.math.RoundingMode

也可以查找特定的函数,比如查找构造函数:

1
sm java.math.RoundingMode <init>

Jad

可以通过 jad 命令来反编译代码:

1
jad com.example.demo.arthas.user.UserController

通过--source-only参数可以只打印出在反编译的源代码:

1
jad --source-only com.example.demo.arthas.user.UserController

Ognl

在Arthas里,有一个单独的ognl命令,可以动态执行代码。

调用static函数

1
ognl '@java.lang.System@out.println("hello ognl")'

获取静态类的静态字段

获取UserController类里的logger字段:

1
ognl -c 1be6f5c3 @com.example.demo.arthas.user.UserController@logger

还可以通过-x参数控制返回值的展开层数。比如:

1
ognl -c 1be6f5c3 -x 2 @com.example.demo.arthas.user.UserController@logger

执行多行表达式,赋值给临时变量,返回一个List

1
2
3
4
5
6
$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
@ArrayList[
@String[/Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/jre],
@String[Java(TM) SE Runtime Environment],
]

更多

在Arthas里ognl表达式是很重要的功能,在很多命令里都可以使用ognl表达式。

一些更复杂的用法,可以参考:

trace

trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。

但是trace只能追踪一层的调用链路,如果一层的链路信息不够用,可以把该链路上有问题的方法再次进行trace。
trace使用例子如下。

trace demo.MathGame primeFactors

tt

录制、回放请求。有时候排查一个问题需要上游再次调用这个方法,比如使用postMan等工具,当然Arthas提供了一个命令让替代来回手动请求。

录制:tt -t demo.MathGame primeFactors

回放:tt -i 1004 -p

注意重放请求需要关注两点:

  • ThreadLocal 信息丢失:由于使用的是Arthas线程调用,会让threadLocal信息丢失,比如一些TraceId信息可能会丢失
  • 引用的对象:保存的入参是保存的引用,而不是拷贝,所以如果参数中的内容被修改,那么入参其实也是被修改的。

查看调用

有时候有些方法非常耗时或者非常重要,需要知道到底是谁发起的调用,比如System.gc(),有时候如果发现fullgc频繁是因为System.gc()引起的,需要查看到底是什么应用调用的。

1
2
options unsafe true
stack java.lang.System gc

首先输入options unsafe true允许对jdk增强,然后对System.gc进行进行监视,然后记录当前的堆栈来获取是什么位置进行的调用。

实践

概述

watch com.xxx.UserController * '{params,throwExp} -x 2

结构:watch 类名 函数名 返回值表达式 展开结果

返回值表达式可选值如下:

  • loader
  • clazz
  • method
  • target
  • params
  • returnObj
  • throwExp
  • isBefore
  • isThrow
  • isReturn

条件表达式

当第一个参数大于100时才打印结果

watch com.example.demo.arthas.user.UserController * returnObj 'params[0] > 100

捕获异常

watch命令支持-e选项,表示只捕获抛出异常时的请求:

1
watch com.example.demo.arthas.user.UserController * "{params[0],throwExp}" -e

按照耗时进行过滤

watch命令支持按请求耗时进行过滤,比如:

1
watch com.example.demo.arthas.user.UserController * '{params, returnObj}' '#cost>200'

热更新代码

保存反编译文件

jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java

查找ClassLoader

1
2
sc -d *UserController | grep classLoaderHash
classLoaderHash 1be6f5c3

修改反编译文件后,重新编译

1
2
3
4
$ mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 346 ms

重新加载新编译后的文件

1
2
redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1

动态更新日志级别

查找ClassLoader

1
2
sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash
classLoaderHash 1be6f5c3

用ognl获取logger

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ognl -c 1be6f5c3 '@com.example.demo.arthas.user.UserController@logger'
@Logger[
serialVersionUID=@Long[5454405123156820674],
FQCN=@String[ch.qos.logback.classic.Logger],
name=@String[com.example.demo.arthas.user.UserController],
level=null,
effectiveLevelInt=@Integer[20000],
parent=@Logger[Logger[com.example.demo.arthas.user]],
childrenList=null,
aai=null,
additive=@Boolean[true],
loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],
]

可以通过loggerContext看出来使用的是logback,并且通过level看出来没有设置级别

单独设置某类的级别

1
ognl -c 1be6f5c3 '@com.example.demo.arthas.user.UserController@logger.setLevel(@ch.qos.logback.classic.Level@DEBUG)'

修改全局日志级别

1
ognl -c 1be6f5c3 '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'

线程

查看所有线程信息

1
thread

查看具体线程的栈

查看线程ID 16的栈:

1
thread 16

查看CPU使用率top n线程的栈

1
thread -n 3

查看5秒内的CPU使用率top n线程栈

1
thread -n 3 -i 5000

查找线程是否有阻塞

1
thread -b

Web Console

通过Web Socket访问Arthas:http://localhost:8563

原理

SC

主要信息,通过Class获取:Test.class.isInterface()

加载信息,通过CodeSource获取:

1
2
CodeSource codeSource = Test.class.getProtectionDomain().getCodeSource();
codeSource.getLocation().getFile();

jad

使用的是cfr提供的jar包来进行反编译

修改日志级别

具体原理是首先获取AppClassLoader(默认)或者指定的ClassLoader,然后再调用Ognl的包,自动执行解析这个表达式,而这个执行的类都会从前面的ClassLoader中获取中去获取。

watch

利用jdk1.6的instrument + ASM 记录方法的入参出参,以及方法消耗时间。

trace

利用jdk1.6的instrument + ASM。在访问方法之前和之后会进行记录

参考文件

Arthas Tutorials

Arthas命令

Katacoda在线学习

Arthas&jvm-sandbox对比