Arthas诊断工具手册
1.安装
下载arthas-boot.jar,然后用java -jar的方式启动:
curl -O https://arthas.aliyun.com/arthas-boot.jar
rm -rf ~/.arthas/
rm -rf ~/logs/arthas
2.启停arthas
java -jar arthas-boot.jar
java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
3.Docker Arthas
FROM openjdk:8-jdk
1.删除本地已有的math-game docker container(非必要)
$ docker stop math-game || true && docker rm math-game || true
2.启动math-game demo应用
$ docker run --name math-game -it hengyunabc/arthas:latest /bin/sh -c "java -jar /opt/arthas/math-game.jar"
3.启动arthas-boot来进行诊断
$ docker exec -it math-game /bin/sh -c "java -jar /opt/arthas/arthas-boot.jar"
4.诊断Docker里的Java进程
docker exec -it ${containerId} /bin/bash -c "wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar"
4.1 诊断k8s里容器里的Java进程
kubectl exec -it ${pod} --container ${containerId} -- /bin/bash -c "wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar"
5.把Arthas安装到基础镜像里,可以很简单把Arthas安装到你的Docker镜像里。
FROM openjdk:8-jdk-alpine
COPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
4.常见问题
1.com.sun.tools.attach.AttachNotSupportedException: Unable to open socket file: target process not responding or HotSpot VM not loaded
答:检查当前用户和目标java进程是否一致。如果不一致,则切换到同一用户。JVM只能attach同样用户下的java 进程。
尝试使用 jstack -l $pid,如果进程没有反应,则说明进程可能假死,无法响应JVM attach信号。所以同样基于attach机制的Arthas无法工作。尝试使用jmap heapdump后分析
2.怎么watch、trace 构造函数 ?
watch demo.MathGame <init> '{params,returnObj,throwExp}' -v
3.方法同名过滤
同名方法过滤可以通过匹配表达式,可以使用表达式核心变量中所有变量作为已知条件,可以通过判断参数个数params.length ==1, 参数类型params[0] instanceof java.lang.Integer、返回值类型 returnObj instanceof java.util.List 等等一种或者多种组合进行过滤。
watch demo.MathGame primeFactors '{params,returnObj,throwExp}' 'params.length >0 && returnObj instanceof java.util.List' -v
5.OGNL表达式
OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,他是一个开源项目。Struts框架使用OGNL作为默认的表达式语言
1.支持对象方法调用,如:×××.doSomeSpecial();
2.支持类静态的方法调用和值访问,表达式的格式
@[类全名(包括包路径)]@[方法名 | 值名],例如:
@java.lang.String@format('foo %s', 'bar')
或@tutorial.MyConstant@APP_NAME;
3.支持赋值操作和表达式串联,
如price=100, discount=0.8,calculatePrice(),这个表达式会返回80;
4.访问OGNL上下文(OGNL context)和ActionContext;
5.操作(创建)集合对象。
1.调用静态属性
ognl '@全路径类目@静态属性名'
2.调用静态方法
ognl '@全路径类目@静态方法名("参数")'
例:-X 2 表示显示的深度,
ognl '@com.shirc.arthasexample.ognl.OgnlTest@getPerson("src",18)' -X 2
3.方法A的返回值当做方法B的入参,并返回一个list
ognl '#value1=@com.shirc.arthasexample.ognl.OgnlTest@getPerson("src",18), #value2=@com.shirc.arthasexample.ognl.OgnlTest@setPerson(#value1) ,{#value1,#value2}' -x 2
4.方法入参是简单类型列表
ognl '@com.shirc.arthxample.ognl.OgnlTest@getChilds({"jinjidelaomanong","jjdlmn"})' -x 2
5.方法入参是一个复杂对象
ognl '#obj=new com.shirc.arthasexample.ognl.Shirc("jjdlmn",true),@com.shirc.arthasexample.ognl.OgnlTest@inputObj(#obj)' -x 2
6.访问复杂对象属性
ognl '@com.shirc.arthasexample.ognl.OgnlTest@getPerson("src",18).name' -x 4
7.访问List或者数组类型
ognl '@com.shirc.arthasexample.ognl.OgnlTest@getChilds({"jinjidelaomanong","jjdlmn"})[0]' -x 2
8. 访问Map对象
ognl '@com.shirc.arthasexample.ognl.OgnlTest@getMap()["shirc"]' -x 2
ognl '@com.shirc.arthasexample.ognl.OgnlTest@getMap()["shirc"].sex' -x 2
shirc: 是map的key; 记得要用双引号"" 引起来
9.
ognl '@com.shirc.arthasexample.ognl.OgnlTest@getMap()["shirc"].(#this.sex=="boy"?"BoyNB":"GirlNB")' -x 2
10.调用构造方法
new 全路径类名()
ognl 'new com.shirc.arthasexample.ognl.Shirc("shirc",true)'
6.常用命令
1.基础命令
2.jvm相关
-i 刷新实时数据的时间间隔 (ms),默认5000ms
-n 刷新实时数据的次数
id 线程id
-n 指定最忙的前N个线程并打印堆栈
-b 找出当前阻塞其他线程的线程
-i 指定cpu使用率统计的采样间隔,单位为毫秒,默认值为200
--all 显示所有匹配的线程
1.THREAD相关
COUNT: JVM当前活跃的线程数
DAEMON-COUNT: JVM当前活跃的守护线程数
PEAK-COUNT: 从JVM启动开始曾经活着的最大线程数
STARTED-COUNT: 从JVM启动开始总共启动过的线程次数
DEADLOCK-COUNT: JVM当前死锁的线程数
2.文件描述符相关
MAX-FILE-DESCRIPTOR-COUNT:JVM进程最大可以打开的文件描述符数
OPEN-FILE-DESCRIPTOR-COUNT:JVM当前打开的文件描述符数
sysprop java.version 查看单个属性
sysprop user.country CN 修改单个属性
sysenv USER 查看单个属性
express 执行的表达式
-c 执行表达式的 ClassLoader 的 hashcode,默认值是SystemClassLoader
需要使用classload获取hashcode,因为hashcode是变化的
--classLoaderClass 唯一实例全类名 指定执行表达式的 ClassLoader 的 class name
-x 结果对象的展开层次,默认值1
vmoption PrintGC 查看一个
vmoption PrintGC true 修改
可以用-d参数打印更多信息
logger -n org.springframework.web 查看指定名字的logger信息
logger -c 2a139a55 需要先获取hashcode
logger -c 2a139a55 --name ROOT --level debug 修改ROOT的日志级别
--include-no-appender logger命令只打印有appender的logger的信息。如果想查看没有appender的logger的信息,可以加上参数
heapdump --live /tmp/dump.hprof 只dump live对象到指定文件
获取对象 --limit 默认是10
vmtool --action getInstances --className java.lang.String --limit 10
强制GC
vmtool --action forceGc
3.class/classloader相关
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
-d 输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。如果一个类被多个ClassLoader所加载,则会出现多次
-E 开启正则表达式匹配,默认为通配符匹配
-f 输出当前类的成员变量信息(需要配合参数-d一起使用)
-x 指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出
-c 指定class的 ClassLoader 的 hashcode
--classLoaderClass 指定执行表达式的 ClassLoader 的 class name
-n 具有详细信息的匹配类的最大数量(默认为100)
用法除了没有 -f -x和sc命令无区别
class-pattern 类名表达式匹配
-c 类所属 ClassLoader 的 hashcode
--classLoaderClass 指定执行表达式的 ClassLoader 的 class name
-E 开启正则表达式匹配,默认为通配符匹配
-c 参数指定classloader
--classLoaderClass 参数指定ClassLoader
-d 命令指定输出目录 mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java
retransform /tmp/Test.class
retransform -l
retransform -d 1
retransform --deleteAll
retransform --classPattern demo.*
retransform -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
retransform --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class
1.reset命令对redefine的类无效。如果想重置,需要redefine原始的字节码。
2.redefine命令和jad/watch/trace/monitor/tt等命令会冲突。执行完redefine之后,如果再执行上面提到的命令,则会把redefine的字节码重置
class-pattern 类名表达式匹配
-c 类所属 ClassLoader 的 hashcode
--classLoaderClass 指定执行表达式的 ClassLoader 的 class name
-d 设置类文件的目标目录
-E 开启正则表达式匹配,默认为通配符匹配
-l 按类加载实例进行统计
-t 打印所有ClassLoader的继承树
-a 列出所有ClassLoader加载的类,请谨慎使用
-c ClassLoader的hashcode
--classLoaderClass 指定执行表达式的 ClassLoader 的 class name
-c hashcode -r resources 用ClassLoader去查找resource
-c hashcode --load class 用ClassLoader去加载指定的类
4.monitor/watch/trace相关
watch/trace/monitor/stack/tt 命令都支持 --exclude-class-pattern 参数;比如:
watch javax.servlet.Filter * --exclude-class-pattern com.demo.TestFilter
watch/stack/trace这个三个命令都支持
1.参数
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
-E 开启正则表达式匹配,默认为通配符匹配
-c 统计周期,默认值为120秒
-b 在方法调用之前计算condition-express
2.监控的维度说明
timestamp 时间戳
class Java类
method 方法(构造方法、普通方法)
total 调用次数
success 成功次数
fail 失败次数
rt 平均RT
fail-rate 失败率
3例:monitor -c 5 demo.MathGame primeFactors "params[0] <= 2"
每5S监视一次方法primeFactors,条件为第一个参数小于2
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
express 观察表达式,默认值:{params, target, returnObj}
condition-express 条件表达式
-b 在方法调用之前观察
-e 在方法异常之后观察
-s 在方法返回之后观察
-f 在方法结束之后(正常返回和异常返回)观察
-E 开启正则表达式匹配,默认为通配符匹配
-x 指定输出结果的属性遍历深度,默认为 1
例:观察耗时超过200ms的MathGame的primeFactors方法的参数和返回list
watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
-E 开启正则表达式匹配,默认为通配符匹配
-n 命令执行次数
--skipJDKMethod false
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
condition-express 条件表达式
-E 开启正则表达式匹配,默认为通配符匹配
-n 执行次数限制
1.参数:
-t 希望记录下类 *Test 的 print方法的每次执行情况。
-n 3 指定需要记录的次数
2.结果显示
INDEX 时间片段记录编号,每一个编号代表着一次调用,后续tt还有很多命令都是基于此编号指定记录操作,非常重要。
TIMESTAMP 方法执行的本机时间,记录了这个时间片段所发生的本机时间
COST(ms) 方法执行的耗时
IS-RET 方法是否以正常返回的形式结束
IS-EXP 方法是否以抛异常的形式结束
OBJECT 执行对象的hashCode(),注意,曾经有人误认为是对象在JVM中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体
CLASS 执行的类名
METHOD 执行的方法名
3.条件表达式
解决方法重载
tt -t *Test print params.length==1
通过制定参数个数的形式解决不同的方法签名,如果参数个数一样,你还可以这样写
tt -t *Test print 'params[1] instanceof Integer'
解决指定参数
tt -t *Test print params[0].mobile=="13989838402"
4.tt -l 查看调用记录 -s 'method.name=="primeFactors"'过滤primeFactors方法的调用记录
5.tt -i index 查看index为index值的调用信息
6.重做一次调用:
tt -i 1004 -p
--replay-times 指定 调用次数,通过
--replay-interval 指定多次调用间隔(单位ms, 默认1000ms)
7.观察表达式
-w, --watch-express 观察时空隧道使用ognl 表达式
例如:获取类的静态字段、调用类的静态方法
tt -w '@demo.MathGame@random.nextInt(100)' -x 1 -i 1000
5.profiler火焰图
profiler 命令基本运行结构是 profiler action [actionArg]
1.启动profiler 默认情况下,生成的是cpu的火焰图,即event为cpu。可以用--event参数来指定
$ profiler start
Started [cpu] profiling
2.获取已采集的sample的数量
$ profiler getSamples
23
3.查看profiler状态
$ profiler status
[cpu] profiling is running for 4 seconds
4.停止profiler
生成html格式结果
默认情况下,结果文件是html格式,也可以用--format参数指定:或者在--file参数里用文件名指名格式。比如--file /tmp/result.html
$ profiler stop --format html
profiler output file: /tmp/test/arthas-output/20211207-111550.html
OK
5.恢复采样
$ profiler resume
Started [cpu] profiling
start和resume的区别是:start是新开始采样,resume会保留上次stop时的数据。
通过执行profiler getSamples可以查看samples的数量来验证。
6.使用execute来执行复杂的命令
比如开始采样:
profiler execute 'start,framebuf=5000000'
停止采样,并保存到指定文件里:
profiler execute 'stop,file=/tmp/result.html'
7.查看所有支持的action
$ profiler actions
Supported Actions: [resume, dumpCollapsed, getSamples, start, list, execute, version, stop, load, dumpFlat, actions, dumpTraces, status]
8.指定执行时间
比如,希望profiler执行 300 秒自动结束,可以用 -d/--duration 参数指定:
profiler start --duration 300
9.配置 include/exclude来过滤数据,include/exclude 都支持设置多个值 ,但是需要配置在命令行的最后;比如
profiler start --include 'java/*' --include 'demo/*' --exclude '*Unsafe.park*'
9.在linux下面支持的event
$ profiler list
Basic events:
cpu
alloc
lock
wall
itimer
Perf events:
page-faults
context-switches
cycles
instructions
cache-references
cache-misses
branches
branch-misses
bus-cycles
L1-dcache-load-misses
LLC-load-misses
dTLB-load-misses
mem:breakpoint
trace:tracepoint
参数名称 | 参数说明 |
---|
action | 要执行的操作 |
actionArg | 属性名模式 |
[i:] | 采样间隔(单位:ns)(默认值:10’000’000,即10 ms) |
[f:] | 将输出转储到指定路径 |
[d:] | 运行评测指定秒 |
[e:] | 要跟踪哪个事件(cpu, alloc, lock, cache-misses等),默认是cpu |
6.options
名称 | 默认值 | 描述 |
---|
unsafe | false | 是否支持对系统级别的类进行增强,打开该开关可能导致把JVM搞挂,请慎重选择! |
dump | false | 是否支持被增强了的类dump到外部文件中,如果打开开关,class文件会被dump到/${application working dir}/arthas-class-dump/ 目录下,具体位置详见控制台输出 |
batch-re-transform | true | 是否支持批量对匹配到的类执行retransform操作 |
json-format | false | 是否支持json化的输出 |
disable-sub-class | false | 是否禁用子类匹配,默认在匹配目标类的时候会默认匹配到其子类,如果想精确匹配,可以关闭此开关 |
support-default-method | true | 是否支持匹配到default method,默认会查找interface,匹配里面的default method。参考 #1105 |
save-result | false | 是否打开执行结果存日志功能,打开之后所有命令的运行结果都将保存到~/logs/arthas-cache/result.log 中 |
job-timeout | 1d | 异步后台任务的默认超时时间,超过这个时间,任务自动停止;比如设置 1d, 2h, 3m, 25s,分别代表天、小时、分、秒 |
print-parent-fields | true | 是否打印在parent class里的filed |
7.用户实战
1.heapdump 下载dump文件,然后使用jprofiler或mat工具分析文件