Java-JVM诊断工具Arthas
https://arthas.aliyun.com/doc/ 官网
介绍
Arthas
是Alibaba开源的Java诊断工具,深受开发者喜爱。
当你遇到以下类似问题而束手无策时,Arthas
可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从JVM内查找某个类的实例?
Arthas
支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab
自动补全功能,进一步方便进行问题的定位和诊断。
注意: 不适用微服务,只适用于单服务代码排查,
比如 A B 两个服务 Arthas安装在A服务上, 那么 A请求B ,使用Arthas只能追踪代码A的所有链路,B服务的链路追踪不了
不闲麻烦的话可以在两个服务商同时都装Arthas ,这样也能实现 ,但是效果就没有微服务链路追踪
好
可以到网上搜索,微服务链路追踪教程
快速安装
创建目录 mkdir /usr/src/jvm
进入目录 cd /usr/src/jvm
注意不能不能直接 curl -O https://arthas.aliyun.com/math-game.jar
需要我们手动去github 上去手动下载https://github.com/alibaba/arthas/releases
如果嫌慢的话我这有网盘版 arthas-3.5.3 的
链接:https://pan.baidu.com/s/19sx20oNxqA_322t3ySt-yA
提取码:1234
解压命令 yum -y install unzip
unzip -o arthas-bin.zip
创建目录 mkdir /usr/src/java
进入目录 cd /usr/src/java
我们这里在提供一个可一直执行的程序(随机数)便于学习arthas
链接:https://pan.baidu.com/s/1Z50cPpNgzJEMtFvD11c_rA
提取码:1234
启动程序
使用FinalSell 或者 XShell 都行
开一个窗口 cd /usr/src/java
启动我们提供的小程序 java -jar math-game.jar
效果如下:
然后在开一个窗口 cd /usr/src/jvm
启动我们的性能监控程序 java -jar arthas-boot.jar
还可以后台启动 java -jar arthas-boot.jar &
如果出现问题可以查看 arthas-boot 的日志在 cd ~/logs/arthas/
下面
启动效果如下:
这种启动方式是自动查询当前服务器上的所有程序 然后手动选取id
进行监控, 就按照上面的,我们直接输入1就行
手动选取指定程序进行监控效果如下:
这个页面就是操作 math-gome的操作台,可以进行各种命令进行监控了
注意: arthas 可以在linux 开启多个客户端监控不同的java,命令还是上面的启动方式
常用命令手册
更全的命令可以在控制台输入 help
Arthas3.5.3命令大全讲解
以下所有命令都是在进入Arthas线程内的控制台使用的
基础命令
NAME | DESCRIPTION |
---|---|
help | 查看命令帮助信息 |
cat | 打印文件内容,和linux里的cat命令类似 |
echo | 打印参数,和linux里的echo命令类似 |
grep | 匹配查找,和linux里的grep命令类似 |
base64 | base64编码转换,和linux里的base64命令类似 |
tee | 复制标准输入到标准输出和指定的文件,和linux里的tee命令类似 |
pwd | 返回当前的工作目录,和linux命令类似 |
cls | 清空当前屏幕区域 |
session | 查看当前会话的信息 |
reset | 重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类 |
version | 输出当前目标 Java 进程所加载的 Arthas 版本号 |
history | 打印命令历史 |
quit | 退出当前 Arthas 客户端,其他 Arthas 客户端不受影响 |
stop | 关闭 Arthas 服务端,所有 Arthas 客户端全部退出 |
只演示常用的
[arthas@126997]$ cls
[arthas@126997]$ reset
Affect(class count: 0 , method count: 0) cost in 1 ms, listenerId: 0
[arthas@126997]$ history
1 dashboard
2 dashboard
3 thread 16
[arthas@126997]$ quit
[root@localhost jvm]#
jvm相关
NAME | DESCRIPTION |
---|---|
dashboard | 当前系统的实时数据面板 |
thread | 查看当前 JVM 的线程堆栈信息 |
jvm | 查看当前 JVM 的信息 |
sysprop | 查看和修改JVM的系统属性 |
sysenv | 查看JVM的环境变量 |
vmoption | 查看和修改JVM里诊断相关的option |
perfcounter | 查看当前 JVM 的Perf Counter信息 |
logger | 查看和修改logger |
getstatic | 查看类的静态属性 |
ognl | 执行ognl表达式 |
mbean | 查看 Mbean 的信息 |
heapdump | dump java heap, 类似jmap命令的heap dump功能 |
vmtool | 从jvm里查询对象,执行forceGc |
dashboard
[arthas@126997]$ dashboard
第一块区域主要显示,cpu的运行情况, 通过这一个区域我们可以观察出来死锁
和死循环
这种特别占用cpu的线程情况
主要 查看为http-nio-xxx-exec-xx的 这种一般都是用户线程访问我们系统了
thread
通过上图我们可以看到main这个线程有点问题,那么我们使用thread这个命令查看 这个线程jvm堆栈信息
语法: thread id
[arthas@126997]$ thread 1
"main" Id=1 TIMED_WAITING
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at demo.MathGame.main(MathGame.java:17)
解读上面代码我们可以发现内部主要是调用了 Thread.sleep 导致拖慢的 具体代码位置也显示出来了
at demo.MathGame.main(MathGame.java:17)
thread -n 3查看当前最忙的前N个线程并打印堆栈
JVM内部信息
[arthas@126997]$ jvm
主要关注:
THREAD相关
- COUNT: JVM当前活跃的线程数
- DAEMON-COUNT: JVM当前活跃的守护线程数
- PEAK-COUNT: 从JVM启动开始曾经活着的最大线程数
- STARTED-COUNT: 从JVM启动开始总共启动过的线程次数
- DEADLOCK-COUNT: JVM当前死锁的线程数 (重点)
FILE-DESCRIPTOR 文件描述符相关
- MAX-FILE-DESCRIPTOR-COUNT:JVM进程最大可以打开的文件描述符数
- OPEN-FILE-DESCRIPTOR-COUNT:JVM当前打开的文件描述符数
getstatic
查看成员变量
语法: getstatic 类路径 变量名
getstatic demo.MathGame random
例如,假设n是一个Map,Map的Key是一个Enum,我们想查询Map对应key的值,可以写如下命令
$ getstatic com.alibaba.arthas.Test n 'entrySet().iterator.{? #this.key.name()=="STOP"}'
field: n
@ArrayList[
@Node[STOP=bbb],
]
Affect(row-cnt:1) cost in 68 ms.
如果key不是一个对象那么
$ getstatic com.alibaba.arthas.Test m 'entrySet().iterator.{? #this.key=="a"}'
field: m
@ArrayList[
@Node[a=aaa],
]
获取静态类的静态字段
语法: ognl ‘@类路径@静态变量’
ognl '@demo.MathGame@random'
class/classloader相关
NAME | DESCRIPTION |
---|---|
sc | 查看JVM已加载的类信息 |
sm | 查看已加载类的方法信息 |
jad | 反编译指定已加载类的源码 |
mc | 内存编译器,内存编译.java文件为.class文件 |
retransform | 加载外部的.class文件,retransform到JVM里 |
dump | dump 已加载类的 byte code 到特定目录 |
classloader | 查看classloader的继承树,urls,类加载信息,使用classloader去getResource |
sc
模糊搜索 ,搜索 demo 目录下所有类文件
[arthas@126997]$ sc demo.*
demo.MathGame
Affect(row-cnt:1) cost in 12 ms.
打印类的详细信息 ,可以知道这个文件到底是接口,还是枚举,还是抽象 … 类型
[arthas@126997]$ sc -d demo.MathGame
class-info demo.MathGame
code-source /usr/src/java/math-game.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@5c647e05
+-sun.misc.Launcher$ExtClassLoader@677327b6
classLoaderHash 5c647e05
Affect(row-cnt:1) cost in 10 ms.
打印出类的Field信息,能查询出来类里面有啥变量具体啥类型
[arthas@126997]$ sc -d -f demo.MathGame
class-info demo.MathGame
code-source /usr/src/java/math-game.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@5c647e05
+-sun.misc.Launcher$ExtClassLoader@677327b6
classLoaderHash 5c647e05
fields name random
type java.util.Random
modifier private,static
value java.util.Random@2a9d2ddf
name illegalArgumentCount
type int
modifier private
Affect(row-cnt:1) cost in 7 ms.
sm
打印类中所有方法…信息
(V)无返回值
[arthas@126997]$ sm demo.MathGame
demo.MathGame <init>()V
demo.MathGame main([Ljava/lang/String;)V
demo.MathGame run()V
demo.MathGame print(ILjava/util/List;)V
demo.MathGame primeFactors(I)Ljava/util/List;
Affect(row-cnt:5) cost in 8 ms.
heapdump
[arthas@126997]$ heapdump /usr/src/java/arthas-output/dump.hprof
Dumping heap to /usr/src/java/arthas-output/dump.hprof ...
Heap dump file created
将当前堆栈,信息输出到指定文件中
jad
反编译类
jad
命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;
- 在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便
- 当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解
jad demo.MathGame
默认情况下,反编译结果里会带有ClassLoader
信息,通过--source-only
选项,可以只打印源代码。
jad --source-only demo.MathGame
反编译指定的函数(方法)
jad --source-only demo.MathGame main
反编译时指定ClassLoader
当有多个
ClassLoader
都加载了这个类时,jad
命令会输出对应ClassLoader
实例的hashcode
,然后你只需要重新执行jad
命令,并使用参数-c <hashcode>
就可以反编译指定 ClassLoader 加载的那个类了
$ jad org.apache.log4j.Logger
Found more than one class for: org.apache.log4j.Logger, Please use jad -c hashcode org.apache.log4j.Logger
HASHCODE CLASSLOADER
69dcaba4 +-monitor's ModuleClassLoader
6e51ad67 +-java.net.URLClassLoader@6e51ad67
+-sun.misc.Launcher$AppClassLoader@6951a712
+-sun.misc.Launcher$ExtClassLoader@6fafc4c2
2bdd9114 +-pandora-qos-service's ModuleClassLoader
4c0df5f8 +-pandora-framework's ModuleClassLoader
Affect(row-cnt:0) cost in 38 ms.
$ jad org.apache.log4j.Logger -c 69dcaba4
monitor/watch/trace相关
请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行
stop
或将增强过的类执行reset
命令。
NAME | DESCRIPTION |
---|---|
monitor | 方法的调用进行监控。 |
watch | 方法执行数据观测 |
trace | 方法内部调用路径,并输出方法路径上的每个节点上耗时 |
stack | 输出当前方法被调用的调用路径 |
tt | 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 |
monitor
方法的调用进行监控。注意不是实时监控, 接口访问后需要过一会才显示
[arthas@51319]$ monitor com.test.web.TestController test
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 45 ms, listenerId: 2
timestamp class method total success fail avg-rt(ms) fail-rate
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2021-08-22 01:41:37 com.test.web.TestController test 1 1 0 10013.89 0.00%
监控的维度说明
监控项 | 说明 |
---|---|
timestamp | 时间戳 |
class | Java类 |
method | 方法(构造方法、普通方法) |
total | 调用次数 |
success | 成功次数 |
fail | 失败次数 |
rt | 平均RT |
fail-rate | 失败率 |
watch
方法执行数据观测 ,让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值
、抛出异常
、入参
特别说明:
- watch 命令定义了4个观察事件点,即
-b
方法调用前,-e
方法异常后,-s
方法返回后,-f
方法结束后 - 4个观察事件点
-b
、-e
、-s
默认关闭,-f
默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出 - 这里要注意
方法入参
和方法出参
的区别,有可能在中间被修改导致前后不一致,除了-b
事件点params
代表方法入参外,其余事件都代表方法出参 - 当使用
-b
时,由于观察事件点是在方法调用前,此时返回值或异常均不存在 - -x 表示观察方法的深度 越大观察的越多
观察方法出参和返回值
watch com.test.web.TestController test "{params,returnObj}" -x 2
观察方法入参
watch com.test.web.TestController test "{params,returnObj}" -x 2 -b
- 事件点为方法执行前,因此获取不到返回值
同时观察方法调用前和方法返回后
watch com.test.web.TestController test "{params,target,returnObj}" -x 2 -b -s -n 2
- 参数里
-n 2
,表示只执行两次 - 这里输出结果中,第一次输出的是方法调用前的观察表达式的结果,第二次输出的是方法返回后的表达式的结果
- 结果的输出顺序和事件发生的先后顺序一致,和命令中
-s -b
的顺序无关
调整-x
的值,观察具体的方法参数值
watch com.test.web.TestController test "{params,target}" -x 3
-
-x
表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1。
条件表达式的例子
watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
- 只有满足条件的调用,才会有响应。
观察异常信息的例子
watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2
-
-e
表示抛出异常时才触发 - express中,表示异常信息的变量是
throwExp
按照耗时进行过滤
watch com.test.web.TestController test '{params, returnObj}' '#cost>200' -x 2
-
#cost>200
(单位是ms
)表示只有当耗时大于200ms时才会输出,过滤掉执行时间小于200ms的调用
观察当前对象中的属性
如果想查看方法运行前后,当前对象中的属性,可以使用target
关键字,代表当前对象 然后.
具体属性
watch demo.MathGame primeFactors 'target.illegalArgumentCount'
trace
方法内部调用路径,并输出方法路径上的每个节点上耗时
trace com.test.web.TestController test
如果方法调用的次数很多,而我只想看一次的,那么可以用-n
参数指定捕捉结果的次数
列: 捕捉到一次调用就退出命令。
trace com.test.web.TestController test -n 1
默认情况下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数,需要显式设置--skipJDKMethod false
。
列:
trace --skipJDKMethod false com.test.web.TestController test -n 1
据调用耗时过滤
列: 只会展示耗时大于100ms的调用路径,有助于在排查问题的时候,只关注异常情况
trace --skipJDKMethod false com.test.web.TestController test -n 1 '#cost > 100'
效果:
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 56 ms, listenerId: 8
`---ts=2021-08-22 02:14:07;thread_name=http-nio-8080-exec-5;id=14;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@3f200884
`---[10001.37477ms] com.test.web.TestController:test()
`---[10001.261073ms] java.lang.Thread:sleep() #32
Command execution times exceed limit: 1, so command will exit. You can set it with -n option.
可以直观看到,具体问题出在哪里了 ``—[10001.261073ms] java.lang.Thread:sleep() #32`
这里存在一个统计不准确的问题,就是所有方法耗时加起来可能会小于该监测方法的总耗时,这个是由于 Arthas 本身的逻辑会有一定的耗时
trace多个类或者多个函数
可以用正则表匹配路径上的多个类的函数,一定程度上达到多层trace的效果
语法: trace -E com.test.ClassA|org.test.ClassB method1|method2|method3
增强深入子函数
[arthas@59161]$ trace --skipJDKMethod false demo.MathGame run '#cost > 1'
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 112 ms, listenerId: 1
`---ts=2020-07-09 16:48:11;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
`---[1.389634ms] demo.MathGame:run()
`---[0.123934ms] demo.MathGame:primeFactors() #24 [throws Exception]
现在想要深入子函数primeFactors
,可以打开一个新终端2,再trace primeFactors
时,指定listenerId
。
trace --skipJDKMethod false demo.MathGame primeFactors --listenerId 1 '#cost > 1'
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 34 ms, listenerId: 1
这时终端2打印的结果,说明已经增强了一个函数:Affect(class count: 1 , method count: 1)
,但不再打印更多的结果。
然后在回到窗口1就会发现 ,打印primeFactors子方法内部的情况了
`---ts=2021-08-22 02:31:05;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@5c647e05
`---[5.965261ms] demo.MathGame:run()
+---[0.037123ms] java.util.Random:nextInt() #23
+---[1.081993ms] demo.MathGame:primeFactors() #24
| `---[1.033554ms] demo.MathGame:primeFactors()
| +---[0.004457ms] java.util.ArrayList:<init>() #49
| `---[min=0.002653ms,max=0.486761ms,total=0.504237ms,count=6] java.util.List:add() #53
`---[0.474074ms] demo.MathGame:print() #25
注意窗口2不能结束,否则窗口1中子方法内容监控不会显示了
stack
输出当前方法被调用的调用路径
很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
列:
[arthas@46551]$ stack demo.MathGame run
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 50 ms, listenerId: 16
ts=2021-08-22 02:48:47;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@5c647e05
@demo.MathGame.run()
at demo.MathGame.main(null:-1)
可以看出来 run方法被main调用了
源码
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
do {
game.run();
TimeUnit.SECONDS.sleep(1L);
} while (true);
}
public void run() throws InterruptedException {//primeFactors...}
public List<Integer> primeFactors(int number) {//...}
可以看出来 primeFactors 被 run()调用 而 run()被main()调用
据执行时间来过滤
[arthas@46551]$ stack demo.MathGame primeFactors '#cost>5'
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 35 ms.
ts=2018-12-04 01:35:58;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
@demo.MathGame.run()
at demo.MathGame.main(MathGame.java:16)
tt
[arthas@46551]$ tt -t demo.MathGame primeFactors -n 3
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 49 ms, listenerId: 17
INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1000 2021-08-22 02:55:16 0.344552 false true 0x15aeb7ab MathGame primeFactors
1001 2021-08-22 02:55:17 1.275065 true false 0x15aeb7ab MathGame primeFactors
-
命令参数解析
-
-t
tt 命令有很多个主参数,
-t
就是其中之一。这个参数的表明希望记录下类*MathGame
的primeFactors
方法的每次执行情况。 -
-n 3
当你执行一个调用量不高的方法时可能你还能有足够的时间用
CTRL+C
中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。此时你可以通过
-n
参数指定你需要记录的次数,当达到记录次数时 Arthas 会主动中断tt命令的记录过程,避免人工操作无法停止的情况。
-
-
表格字段说明
表格字段 | 字段解释 |
---|---|
INDEX | 时间片段记录编号,每一个编号代表着一次调用,后续tt还有很多命令都是基于此编号指定记录操作,非常重要。 |
TIMESTAMP | 方法执行的本机时间,记录了这个时间片段所发生的本机时间 |
COST(ms) | 方法执行的耗时 |
IS-RET | 方法是否以正常返回的形式结束 |
IS-EXP | 方法是否以抛异常的形式结束 |
OBJECT | 执行对象的hashCode() ,注意,曾经有人误认为是对象在JVM中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体 |
CLASS | 执行的类名 |
METHOD | 执行的方法名 |
-
条件表达式
不知道大家是否有在使用过程中遇到以下困惑
- Arthas 似乎很难区分出重载的方法
- 我只需要观察特定参数,但是 tt 却全部都给我记录了下来
条件表达式也是用
OGNL
来编写,核心的判断对象依然是Advice
对象。除了tt
命令之外,watch
、trace
、stack
命令也都支持条件表达式。 -
解决方法重载
tt -t demo.MathGame primeFactors params.length==1
通过制定参数个数的形式解决不同的方法签名,如果参数个数一样,你还可以这样写
tt -t demo.MathGame primeFactors 'params[1] instanceof Integer'
检索调用记录
当你用 tt
记录了一大片的时间片段之后,你希望能从中筛选出自己需要的时间片段,这个时候你就需要对现有记录进行检索。
假设我们有这些记录
$ tt -l
INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD
-------------------------------------------------------------------------------------------------------------------------------------
1000 2018-12-04 11:15:38 1.096236 false true 0x4b67cf4d MathGame primeFactors
1001 2018-12-04 11:15:39 0.191848 false true 0x4b67cf4d MathGame primeFactors
1002 2018-12-04 11:15:40 0.069523 false true 0x4b67cf4d MathGame primeFactors
1003 2018-12-04 11:15:41 0.186073 false true 0x4b67cf4d MathGame primeFactors
1004 2018-12-04 11:15:42 17.76437 true false 0x4b67cf4d MathGame primeFactors
9
1005 2018-12-04 11:15:43 0.4776 false true 0x4b67cf4d MathGame primeFactors
Affect(row-cnt:6) cost in 4 ms.
我需要筛选出 primeFactors
方法的调用信息
$ tt -s 'method.name=="primeFactors"'
查看调用信息
对于具体一个时间片的信息而言,你可以通过 -i
参数后边跟着对应的 INDEX
编号查看到他的详细信息。
$ tt -i 1003
INDEX 1003
GMT-CREATE 2018-12-04 11:15:41
COST(ms) 0.186073
OBJECT 0x4b67cf4d
CLASS demo.MathGame
METHOD primeFactors
IS-RETURN false
IS-EXCEPTION true
PARAMETERS[0] @Integer[-564322413]
THROW-EXCEPTION java.lang.IllegalArgumentException: number is: -564322413, need >= 2
at demo.MathGame.primeFactors(MathGame.java:46)
at demo.MathGame.run(MathGame.java:24)
at demo.MathGame.main(MathGame.java:16)
Affect(row-cnt:1) cost in 11 ms.
重做一次调用
当你稍稍做了一些调整之后,你可能需要前端系统重新触发一次你的调用,此时得求爷爷告奶奶的需要前端配合联调的同学再次发起一次调用。而有些场景下,这个调用不是这么好触发的。
tt
命令由于保存了当时调用的所有现场信息,所以我们可以自己主动对一个 INDEX
编号的时间片自主发起一次调用,从而解放你的沟通成本。此时你需要 -p
参数。通过 --replay-times
指定 调用次数,通过 --replay-interval
指定多次调用间隔(单位ms, 默认1000ms)
tt -i 1004 -p
RE-INDEX 1004
GMT-REPLAY 2018-12-04 11:26:00
OBJECT 0x4b67cf4d
CLASS demo.MathGame
METHOD primeFactors
PARAMETERS[0] @Integer[946738738]
IS-RETURN true
IS-EXCEPTION false
COST(ms) 0.186073
RETURN-OBJ @ArrayList[
@Integer[2],
@Integer[11],
@Integer[17],
@Integer[2531387],
]
Time fragment[1004] successfully replayed.
Affect(row-cnt:1) cost in 14 ms.
你会发现有可能结果一样,但调用的路径发生了变化,由原来的程序发起变成了 Arthas 自己的内部线程发起的调用了。
管道
后台异步任务
生成火焰图统计图
SVG效果图
HTMl效果图(推荐)
启动profiler
profiler start
默认情况下,生成的是cpu的火焰图,即event为cpu
。可以用--event
参数来指定。
获取已采集的sample的数量
profiler getSamples
查看profiler状态
profiler status
停止profiler并且生成avg文件
profiler stop
停止profiler并且生成html文件
profiler stop --format html
线上动态热编译
我这和网上教程不一样,网上的是使用 jad + mc + retransform 进行线上热更新的,
但是我发现很多时候在使用mc编译过程总是失败各种问题,然后我自己总结了一套方案
- 使用IDEA…软件能将项目编译成.class文件
然后将代码复制到linux上 cd /usr/src/java/arthas-output
-
使用 retransform 将JVM里代码替换
[arthas@14001]$ retransform /usr/src/java/arthas-output/TestController.class retransform success, size: 1, classes:
retransform的限制
- 不允许新增加field(成员变量)/method(类中方法)
- 正在跑的函数,没有退出不能生效,比如下面新增加的
System.out.println
,只有run()
函数里的会生效
public class MathGame {
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
while (true) {
game.run();
TimeUnit.SECONDS.sleep(1);
// 这个不生效,因为代码一直跑在 while里
System.out.println("in loop");
}
}
public void run() throws InterruptedException {
// 这个生效,因为run()函数每次都可以完整结束
System.out.println("call run()");
try {
int number = random.nextInt();
List<Integer> primeFactors = primeFactors(number);
print(number, primeFactors);
} catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
}
}
注意: retransform 修改的是内存不是源码, 当系统重启后都会还原,所以只能使用于测试,或者临时应付
如果想要直接作用 在jar源码和jvm中那么我们可以使用 redefine 来代替