元月's blog 元月's blog
首页
  • 基础
  • 并发编程
  • JVM
  • Spring
  • Redis篇
  • Nginx篇
  • Kafka篇
  • Otter篇
  • Shardingsphere篇
  • 设计模式
  • MySQL
  • Oracle
  • 基础
  • 操作系统
  • 网络
  • 数据结构
  • 技术文档
  • Git常用命令
  • GitHub技巧
  • 博客搭建
  • 开发工具
更多

元月

临渊羡鱼,不如退而结网
首页
  • 基础
  • 并发编程
  • JVM
  • Spring
  • Redis篇
  • Nginx篇
  • Kafka篇
  • Otter篇
  • Shardingsphere篇
  • 设计模式
  • MySQL
  • Oracle
  • 基础
  • 操作系统
  • 网络
  • 数据结构
  • 技术文档
  • Git常用命令
  • GitHub技巧
  • 博客搭建
  • 开发工具
更多
  • 基础

  • 并发编程

  • JVM

    • Java的调试体系-JPDA架构
    • JVM整体结构和内存模型
    • 深度剖析JVM类加载机制
    • JVM对象创建与内存分配机制
    • JVM垃圾回收算法
    • JVM垃圾收集器一:Serial和Parallel收集器
    • JVM垃圾收集器二:CMS与三色标记算法详解
    • JVM垃圾收集器三:G1(Garbage First)
    • JVM垃圾收集器四:ZGC与颜色指针详解
    • JVM调优之常用的调优指令
    • JVM调优之常用的调优工具
    • Arthas:一款优秀的Java诊断工具
      • 一、简介
      • 二、快速入门
        • 1. 启动 math-game
        • 2. 启动 arthas
      • 三、常用命令
        • 1. 基本命令
        • 1.1 dashboard
        • 1.2 thread
        • 1.3 jad 反编译指定已加载类的源码
        • 1.4 执行 ognl 表达式
        • 2. 进阶命令
        • 2.1 trace 查看方法内部调用路径,并输出方法路径上的每个节点上耗时
        • 2.2 watch 函数执行数据观测
        • 2.3 tt 时空隧道
        • 3.获取JVM中某个对象的内容
      • 四、Idea插件-arthas idea的使用
        • 1. 安装
        • 2. 使用
        • 3.通过arthas插件调用静态函数
      • 五、FAQ
        • 5.1、class redefinition failed: attempted to change the scheam
    • 亿级流量系统JVM实战
  • Java基础
  • JVM
元月
2022-09-07
目录

Arthas:一款优秀的Java诊断工具

# Arthas:一款优秀的Java诊断工具

# 一、简介

Arthas 是一款非常优秀的线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并且能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大的提高线上问题的排查效率。

阿里云Arthas (opens new window)、Gitee Arthas (opens new window)

# 二、快速入门

# 1. 启动 math-game

curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar
1
2

math-game是一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。

# 2. 启动 arthas

在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败):

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
1
2
  • 执行该程序的用户需要和目标进程具有相同的权限。比如以admin用户来执行:sudo su admin && java -jar arthas-boot.jar 或 sudo -u admin -EH java -jar arthas-boot.jar。
  • 如果 attach 不上目标进程,可以查看~/logs/arthas/ 目录下的日志。
  • 如果下载速度比较慢,可以使用 aliyun 的镜像:java -jar arthas-boot.jar --repo-mirror aliyun --use-http
  • java -jar arthas-boot.jar -h 打印更多参数信息。

选择应用 java 进程:

$ $ java -jar arthas-boot.jar
* [1]: 35542
  [2]: 71560 math-game.jar
1
2
3

math-game进程是第 2 个,则输入 2,再输入回车/enter。Arthas 会 attach 到目标进程上,并输出日志:

[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'

wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
1
2
3
4
5
6
7
8
9
10
11
12
13

# 三、常用命令

# 1. 基本命令

# 1.1 dashboard

​ 查看整个进程的运行情况,线程、内存、GC、运行环境信息

img

# 1.2 thread

thread id, 显示指定线程的运行堆栈

$ thread 1
"main" Id=1 WAITING on java.util.concurrent.CountDownLatch$Sync@29fafb28
    at sun.misc.Unsafe.park(Native Method)
    -  waiting on java.util.concurrent.CountDownLatch$Sync@29fafb28
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
    at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
1
2
3
4
5
6
7
8
9

thread -b, 找出当前阻塞其他线程的线程

thread -i, 指定采样时间间隔

  • thread -i 1000 : 统计最近 1000ms 内的线程 CPU 时间。
  • thread -n 3 -i 1000 : 列出 1000ms 内最忙的 3 个线程栈
$ thread -n 3 -i 1000
"as-command-execute-daemon" Id=4759 cpuUsage=23% RUNNABLE
    at sun.management.ThreadImpl.dumpThreads0(Native Method)
    at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:440)
    at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:133)
    at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:79)
    at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:96)
    at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:27)
    at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:125)
    at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:122)
    at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:332)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:756)

    Number of locked synchronizers = 1
    - java.util.concurrent.ThreadPoolExecutor$Worker@546aeec1
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1.3 jad 反编译指定已加载类的源码

默认情况下,反编译结果里会带有ClassLoader信息,通过--source-only选项,可以只打印源代码。方便和mc (opens new window)/retransform (opens new window)命令结合使用。

$ jad --source-only demo.MathGame
/*
 * Decompiled with CFR 0_132.
 */
package demo;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class MathGame {
    private static Random random = new Random();
    public int illegalArgumentCount = 0;
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1.4 执行 ognl 表达式

打印类的详细信息

$ sc -d com.example.demo.util.UserUtil
 class-info        com.example.demo.util.UserUtil
 code-source       /D:/project/20240201demo/spring-mybatis-demo/target/classes/
 name              com.example.demo.util.UserUtil
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       UserUtil
 modifier          public
 annotation
 interfaces
 super-class       +-java.lang.Object
 class-loader      +-sun.misc.Launcher$AppClassLoader@18b4aac2
                     +-sun.misc.Launcher$ExtClassLoader@353d0772
 classLoaderHash   18b4aac2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

调用静态函数:

#需要带上上一步获取到的classLoaderHash
$ ognl -c 18b4aac2 -x 3 '@com.example.demo.util.UserUtil@get("123")'
@User[
    id=null,
    name=@String[123],
    updateTime=null,  
    createTime=null,  
]
1
2
3
4
5
6
7
8

# 2. 进阶命令

# 2.1 trace 查看方法内部调用路径,并输出方法路径上的每个节点上耗时
$ trace demo.MathGame run  -n 5 --skipJDKMethod false '#cost > 10'
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 41 ms.
`---ts=2018-12-04 01:12:02;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
    `---[12.033735ms] demo.MathGame:run()
        +---[0.006783ms] java.util.Random:nextInt()
        +---[11.852594ms] demo.MathGame:primeFactors()
        `---[0.05447ms] demo.MathGame:print()
1
2
3
4
5
6
7
8
# 2.2 watch 函数执行数据观测
$ watch demo.MathGame primeFactors -x 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 32 ms, listenerId: 5
method=demo.MathGame.primeFactors location=AtExceptionExit
ts=2021-08-31 15:22:57; [cost=0.220625ms] result=@ArrayList[
    @Object[][
        @Integer[-179173],
    ],
    @MathGame[
        random=@Random[java.util.Random@31cefde0],
        illegalArgumentCount=@Integer[44],
    ],
    null,
]
method=demo.MathGame.primeFactors location=AtExit
ts=2021-08-31 15:22:58; [cost=1.020982ms] result=@ArrayList[
    @Object[][
        @Integer[1],
    ],
    @MathGame[
        random=@Random[java.util.Random@31cefde0],
        illegalArgumentCount=@Integer[44],
    ],
    @ArrayList[
        @Integer[2],
        @Integer[2],
        @Integer[26947],
    ],
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  • 上面的结果里,说明函数被执行了两次,第一次结果是location=AtExceptionExit,说明函数抛出异常了,因此returnObj是 null
  • 在第二次结果里是location=AtExit,说明函数正常返回,因此可以看到returnObj结果是一个 ArrayList
# 2.3 tt 时空隧道

方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测

记录调用

对于一个最基本的使用来说,就是记录下当前方法的每次调用环境现场。

$ tt -t demo.MathGame primeFactors
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 66 ms.
 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
1
2
3
4
5
6
7
8
9
10

查看调用信息

对于具体一个时间片的信息而言,你可以通过 -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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

重做一次调用

当你稍稍做了一些调整之后,你可能需要前端系统重新触发一次你的调用,此时得求爷爷告奶奶的需要前端配合联调的同学再次发起一次调用。而有些场景下,这个调用不是这么好触发的。

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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.获取JVM中某个对象的内容
#获取tomcat线程池配置
vmtool -x 3 --action getInstances --className org.springframework.boot.autoconfigure.web.ServerProperties  --express 'instances[0].getTomcat()'
1
2

详见:arthas vmtool (opens new window)

# 四、Idea插件-arthas idea的使用

# 1. 安装

# 2. 使用

在代码的类上或方法上右键即可,省去自行拼接类名、方法名等

# 3.通过arthas插件调用静态函数

step1: 在方法右键,如下图

step2: 点击 copy sc command 执行后获取类的classLoaderHash信息

$ sc -d com.example.demo.util.UserUtil
 class-info        com.example.demo.util.UserUtil
 code-source       /D:/project/20240201demo/spring-mybatis-demo/target/classes/
 name              com.example.demo.util.UserUtil
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       UserUtil
 modifier          public
 annotation
 interfaces
 super-class       +-java.lang.Object
 class-loader      +-sun.misc.Launcher$AppClassLoader@18b4aac2
                     +-sun.misc.Launcher$ExtClassLoader@353d0772
 classLoaderHash   18b4aac2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

step3: 点击copy command复制,调用静态函数

#需要带上上一步获取到的classLoaderHash
$ ognl -c 18b4aac2 -x 3 '@com.example.demo.util.UserUtil@get("123")'
@User[
    id=null,
    name=@String[123],
    updateTime=null,  
    createTime=null,  
]
1
2
3
4
5
6
7
8

# 五、FAQ

# 5.1、class redefinition failed: attempted to change the scheam

trace demo.MathGame run  -n 5 --skipJDKMethod false '#cost > 10'
1

在生产环境执行trace命令时出现如下报错

java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the scheam (add/remove fields)
1

问题原因:

arthas在修改类结构的时候发现已经被修改了,所以抛出异常。定位到是因为skywalking兼容arthas的问题,Skywalking探针会对Controller层的方法进行改写,导致arthas 相关命令监控失败,如果需要监控Controller层,则需要关闭Skywalking,service层和mapper层不影响。

解决方法:

skywalking提供两种解决方案(本人暂未验证,参考:skywalking兼容arthas问题 (opens new window))

  • 服务启动的时候开启增强类的缓存功能,添加如下启动参数

    -Dskywalking.agent.is_cache_enhanced_class=true -Dskywalking.agent.class_cache_mode=MEMORY
    
    1
  • 修改代理类配置文件agent.conf

    agent.is_cache_enhanced_class = ${SW_AGENT_CACHE_CLASS:false}
    agent.class_cache_mode = ${SW_AGENT_CLASS_CACHE_MODE:MEMORY}
    
    1
    2
JVM调优之常用的调优工具
亿级流量系统JVM实战

← JVM调优之常用的调优工具 亿级流量系统JVM实战→

最近更新
01
otter二次开发-支持按目标端主键索引Load数据
08-03
02
mvnw简介
06-21
03
gor流量复制工具
06-03
更多文章>
Theme by Vdoing | Copyright © 2022-2024 元月 | 粤ICP备2022071877号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式