Java-JVM诊断工具Arthas

Java-JVM诊断工具Arthas

https://arthas.aliyun.com/doc/ 官网

介绍

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?
  7. 怎么快速定位应用的热点,生成火焰图?
  8. 怎样直接从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

效果如下:

Java-JVM诊断工具Arthas

然后在开一个窗口 cd /usr/src/jvm

启动我们的性能监控程序 java -jar arthas-boot.jar

还可以后台启动 java -jar arthas-boot.jar &

如果出现问题可以查看 arthas-boot 的日志在 cd ~/logs/arthas/ 下面

启动效果如下:

Java-JVM诊断工具Arthas

这种启动方式是自动查询当前服务器上的所有程序 然后手动选取id进行监控, 就按照上面的,我们直接输入1就行

手动选取指定程序进行监控效果如下:

Java-JVM诊断工具Arthas

这个页面就是操作 math-gome的操作台,可以进行各种命令进行监控了

注意: arthas 可以在linux 开启多个客户端监控不同的java,命令还是上面的启动方式

常用命令手册

Java-JVM诊断工具Arthas

更全的命令可以在控制台输入 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

Java-JVM诊断工具Arthas

第一块区域主要显示,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 就是其中之一。这个参数的表明希望记录下类 *MathGameprimeFactors 方法的每次执行情况。

    • -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 命令之外,watchtracestack 命令也都支持条件表达式。

  • 解决方法重载

    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效果图

Java-JVM诊断工具Arthas

HTMl效果图(推荐)

Java-JVM诊断工具Arthas

启动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编译过程总是失败各种问题,然后我自己总结了一套方案

  1. 使用IDEA…软件能将项目编译成.class文件

Java-JVM诊断工具Arthas

然后将代码复制到linux上 cd /usr/src/java/arthas-output

  1. 使用 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 来代替

点赞 -收藏-关注-便于以后复习和收到最新内容
有其他问题在评论区讨论-或者私信我-收到会在第一时间回复
如有侵权,请私信联系我
感谢,配合,希望我的努力对你有帮助^_^
上一篇:为何曾经流行的匈牙利命名法忽然间销声匿迹了


下一篇:.Net 下高性能分表分库组件-连接模式原理