启动arthas
$JDK_ROOT/jdk*/bin/java -Dfile.encoding=UTF-8 -jar $ARTHAS_ROOT/arthas-boot.jar [tomcatPID]
# 例如一个最简单的命令:./java -jar arthas-boot.jar 2198
profiler命令 - 性能分析与火焰图
可以用profiler命令生成火焰图
profiler action [actionArg]
最简单的profiler命令
// 启动
profiler start
// 获取已采集数量
profiler getSamples
// 查看profiler状态
profiler status
// 停止profiler并生成html或svg文档输出,也可以通过--file指定输出文件名和路径
profiler stop --format html [--file /tmp/result.svg]
// 可以使用--event来指定生成什么资源的火焰图,不指定默认为cpu的
// --format指定格式
monitor命令 - 监控类与方法的执行
可以使用monitor语法做类-方法监控,语句如下:
monitor 类引用全类名 方法名 -c 5 // -c 表示统计周期,多久刷新一次
输出结果:
timestamp 时间戳
class Java类
method 方法(构造方法、普通方法)
total 调用次数
success 成功次数
fail 失败次数
rt 平均耗时
fail-rate 失败率
watch命令 - 监控方法执行时的对象、入参、返回值
watch命令的基本语法如下:
watch 类引用全类名 方法名 [观察表达式] [观察点] [条件表达式] [-x n]
假设有方法boolean TestTask(String)
观察表达式
可以自己写输出的格式,如果不写,默认输出格式为:入参、执行主体对象、返回值,参考以下案例:
基本的观察语句
watch com.test.TestTask test1
@Object[][isEmpty=false;size=1],
@testTask[com.test.TestTask@14db58af],
@Boolean[true]
只观察入参和返回值
watch com.test.TestTask test1 "{params,returnObj}" // 指定表达式为入参列表和出参,因此不返回对象
@Object[][isEmpty=false;size=1],
@Boolean[true]
只观察第0个入参和返回值
watch com.test.TestTask test1 "{params[0],returnObj}" // 指定返回第0参数,因此返回了String入参
@String[testBegin],
@Boolean[true]
指定第x个时要注意,如果只有一个入参,指定后面的,会报错,类似数组超长
watch com.test.TestTask test1 “{params[1],returnObj}” // 只有一个入参,指定返回第1个,报错
只观察执行对象和其成员变量
watch com.test.TestTask test1 “{target}” // 只输出执行主体对象
@testTask[com.test.TestTask@14db58af],
这里可以很方便地查看方法执行时,对象本身的状态,结合-x
参数即可
观察执行对象时还可以输出对象的某一个特定属性
watch com.test.TestTask test1 “{target.testName}” // 在对象上加属性名可以打出具体属性
@testTask[
testName=@String[myTest],
],
-x参数 - 深度递归
-x n写法,不写默认递归深度为1,参考以下案例:
下面的案例要求返回入参列表,但是递归深度为2,因此输出了列表里面的元素
watch com.test.TestTask test1 "{params,returnObj}" -x 2
@Object[][
@String[hello world],
],
@Boolean[true]
下面的案例返回参数2是String只有一层,因此递归2也是String一个
watch com.test.TestTask test1 "{params[0],returnObj}" -x 2
@String[testBegin],
@Boolean[true]
下面的案例要求返回对象中属性的深层表达,即当属性是其他对象时,可以输出其中的内容
watch com.test.TestTask test1 “{target}” -x 2
@testTask[
testName=@String[myTest],
testIndex=@Integer[6],
],
观察点
watch 命令定义了4个观察事件点:
-b
方法调用前-e
方法异常后-s
方法返回后-f
方法结束后
默认情况下不写为-f
下面的案例因为是执行前开始输出,因此返回值为null
watch com.test.TestTask test1 "{params[0],returnObj}" -b
@String[testBegin],
null
下面的案例加两个观察点,这样就输出两次
watch com.test.TestTask test1 "{params[0],returnObj}" -b -s
@String[testBegin],
null
@String[testBegin],
@Boolean[true]
条件表达式
下面的案例假如入参1是int类型,就可以使用条件表达式写法了
watch com.test.TestTask test1 "{params[0],target}" "params[0]<2"
@Integer[1],
@Boolean[true]
trace命令 - 监控调用步骤耗时
trace命令的基本语法如下:
trace 类引用全类名 方法名 [条件表达式] [-n n]
trace使用实例如下:
trace com.test.TestTask test1
// thread_name:线程名称;id:线程id;is_daemon:是否为守护线程;TCCL:类加载器
// 后面开始输出每一个内部方法的耗时,方法后面的#数字是行号
trace com.test.TestTask test1 -n 1 // 执行一次后退出
跳过jdk方法
trace --skipJDKMethod false com.test.TestTask test1 -n 1 // 默认跳过JDK方法,若要执行需要手动false
条件表达式和正则匹配
trace com.test.TestTask test ‘#cost > .5’ // 输出耗时大于0.5s的方法
trace -E com.test.ClassA|org.test.ClassB method1|method2|method3 // 正则匹配
stack命令 - 监控方法被调用的调用路径
很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者根本就不知道这个方法是从那里被执行了,此时可以使用 stack 命令
stack命令的基本语法如下:
stack类引用全类名 方法名 [条件表达式] [-n n]
stack使用实例如下:
stack com.test.TestTask test1
stack com.test.TestTask test1 'params[0]<0' -n 1 // 输出入参0小于的堆栈,且执行一次后退出
stack com.test.TestTask test '#cost > .5' // 输出耗时大于0.5s的方法调用堆栈
tt命令 - 时间隧道
记录下指定方法每次调用的入参和返回信息,并能对这些不同时间下调用的信息进行观测。watch虽然好用,但是有时候可能并不知道问题出在哪,也没有办法做精准的watch。这时候就可以使用tt进行记录
tt语法的常用参数包括:
-t 表明希望记录下类 *Test 的 print 方法的每次执行情况
-n 记录次数
tt使用实例如下:
tt -t com.test.TestTask test1
// index:编号;TIMESTAMP:执行时间;COST:耗时;IS-RET:是否正常返回
// OBJECT:对象的hashcode;CLASS:执行的类名(非内存地址);METHOD:方法名
tt -l // 对现有记录进行检索,可以再次输出当前记录的内容
tt -s // 搜索特定
tt -s 'method.name=="test1"' // 输出方法名为method1的
tt -s 'params[2].getRecordId() == "1123154"' // 搜索第三个参数的RecordId符合预期的
tt -i 1002 // 查看1002记录的详细信息,可以看到方法执行的入参返回值
tt的重载能力
可以自己主动对一个 INDEX 编号的时间片自主发起一次调用,从而解放你的沟通成本。此时你需要 -p 参数。通过 --replay-times
指定 调用次数,通过 --replay-interval
指定多次调用间隔(单位ms, 默认1000ms)
tt -i 1002 -p
tt -i 1002 -p --replay-interval 3 // 再重新调用3次
tt -i 1008 -p --replay-times 3 --replay-interval 2000 // 再重新调用3次,并且间隔2S
jad - 反编译
jad --source-only com.test.TestTask test1
jvm命令
dashboard - 实时数据面板
直接使用dashboard
命令可以输出:
ID:线程ID;
NAME:线程名字;
GROUP:线程组;
PRIORITY:优先级:
STATE:线程状态
%CPU:CPU占比;
TIME:运行时长;
INTERRUPTE:中断状态;
DAEMON:后台线程
内存和垃圾回收器相关
运行环境JVM参数
thread - 线程命令
thread // 显示所有线程的信息,STATE为blocked则为阻塞线程
thread 1 // 显示1号线程的运行堆栈
thread -b // 查看阻塞的线程信息
thread -n 3 // 查看最忙的3个线程,并打印堆栈
thread -i 1000 -n 3 // 指定采样时间间隔,每过1000毫秒采样,显示最占时间的3个线程
thread 1 | grep 'main(' // 查找指定线程
thread --state WAITING // 查看处于等待状态的线程(WAITING、BLOCKED)
thread -b // 查看阻塞的线程信息
jvm - 环境命令
// 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当前打开的文件描述符数
sysprop - 查看/修改属性
sysprop // 查看所有属性
sysprop java.version // 查看单个属性,支持通过tab补全
也可以支持属性修改
sysprop user.country
user.country=US
sysenv - 查看JVM环境变量
# 查看所有环境变量
sysenv
# 查看单个环境变量
sysenv USER
vmpotion - 查看/修改JVM选项
# 查看所有的选项
vmoption
# 查看指定的选项
vmoption PrintGCDetails
# 更新指定的选项
vmoption PrintGCDetails true
getstatic - 获取静态变量
# 语法
getstatic 类名 属性名
# 显示demo.MathGame类中静态属性random
getstatic demo.MathGame random
也可以加 -x n表示递归多少层
ognl - 执行ognl表达式
ognl用于执行表达式
注意:类和方法前面用@,方法加括号,其中放变量
# 获取系统变量中值,并且打印(只会打印有返回值函数)
ognl '@java.lang.System@out.println("hello")' 第一个@是类,第二个@是方法,输出返回值
# 获取代码中的运行返回值
ognl '@demo.MathGame@random'
# 计算value1、value2值,并存在List集合中
ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
ognl有时会出现找不到类的问题,可以通过sc进行测试,如果找得到,可以通过指定类加载器的形式执行
# 首先找类加载器,如果能找到,输出以下内容:
sc -d demo.MathGame
-- classLoaderHash 688c41f3 --
# 通过指定类加载器执行表达式
ognl -c 688c41f3 ‘@demo.MathGame@random’
ognl也支持级联调用,例如使用一些spring工具获取bean,执行对应的对象方法
ognl 'ApplicationUtils.getContext().getBean(‘testBean’).doSomething(0)'
类加载器相关
sc - 查看类信息
sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true
开关
# 模糊搜索,demo包下所有的类
sc demo.*
# -d 打印类的详细信息;-f 输出当前类成员变量信息,可以查看类加载器
sc -d demo.MathGame
sm - 查看已加载的方法信息
# 显示String类加载的方法
sm java.lang.String
# 查看方法信息
sm demo.MathGame
# 查看方法信息(详细信息-d)
sm -d demo.MathGame
jad、mc、redefine - 编译与反编译
jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;
# 反编译MathGame方法
jad demo.MathGame
# 反编绎时只显示源代码(排除ClassLoader信息)。
# 默认情况下,反编译结果里会带有ClassLoader信息,通过--source-only选项,可以只打印源代码。方便和mc/redefine命令结合使用。
jad --source-only demo.MathGame
# 反编译到指定文件中
jad --source-only demo.MathGame > Hello.java
# 只反编译mathGame类型中main方法
jad demo.MathGame main
mc是内存编译,Memory Compiler/内存编译器,编译.java文件生成.class
# 在内存中编译Hello.java为Hello.class
mc /root/Hello.java
# 可以通过-d命令指定输出目录
mc -d /root/bbb /root/Hello.java
redefine加载外部的.class文件到JVM里,但redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field)。
reset命令对redefine的类无效。如果想重置,需要redefine原始的字节码。
redefine命令和jad/watch/trace/monitor/tt等命令会冲突。执行完redefine之后,如果再执行上面提到的命令,则会把redefine的字节码重置。
redefine的限制:
不允许新增加field/method
正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效。
# 1.使用jad反编译demo.MathGame输出到/root/MathGame.java
jad --source-only demo.MathGame > /root/MathGame.java
# 2.按上面的代码编辑完毕以后,使用mc内存中对新的代码编译
mc /root/MathGame.java -d /root
# 3.使用redefine命令加载新的字节码
redefine /root/demo/MathGame.class
classloader - 获取类加载器相关信息
classloader命令将JVM中所有的classloader的信息统计出来,并可以展示继承树,urls等,也可以让指定的classloader去getResources,打印出所有查找到的resources的url。对于ResourceNotFoundException异常比较有用
# 默认按类加载器的类型查看统计信息
classloader
# 按类加载器的实例查看统计信息,可以看到类加载的hashCode
classloader -l
# 查看ClassLoader的继承树
classloader -t
# 通过类加载器的hash,查看此类加载器实际所在的位置
classloader -c 680f2737
# 使用ClassLoader去查找指定资源resource所在的位置
classloader -c 680f2737 -r META-INF/MANIFEST.MF
# 使用ClassLoader去查找类的class文件所在的位置
classloader -c 680f2737 -r java/lang/String.class
# 使用ClassLoader去加载类
classloader -c 70dea4e --load java.lang.String
其他指令
# 还原增强类,不加类则还原所有
reset [demo.MathGame]
# 使用json格式输出结果
options json-format true
评论区