目 录CONTENT

文章目录

arthas - 强大的运维工具

FatFish1
2024-12-10 / 0 评论 / 0 点赞 / 83 阅读 / 0 字 / 正在检测是否收录...

启动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

0

评论区