Arthas使用指南

Arthas 能为你做什么?

  • 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在预发 debug 一下,难道只能通过加日志再重新预发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现,怎么办?
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到容器和中间件的实时运行状态?

Arthas 是基于 Greys 进行二次开发的全新在线诊断工具,利用Java6的Instrumentation特性,动态增强你所指定的类,获取你想要到的信息, 采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,让你在定位、分析诊断问题时看每一个操作都看起来是那么的 666

阿里已开源 https://github.com/alibaba/ar...

Arthas安装及使用

下载压缩包,上传到需要被诊断的机器, 解压缩

  • 安装: 执行 ./install.sh
  • 启动: 执行 ./as.sh pid

常用命令

dashboard

当前系统的实时数据面板

thread

查看当前 JVM 的线程堆栈信息

jvm

查看当前 JVM 的信息

sc

查看JVM已加载的类信息

sm

查看已加载类的方法信息

jad

反编译指定已加载类的源码

classloader

查看classloader的继承树,urls,类加载信息,使用classloader去getResource

monitor

方法执行监控

watch

方法执行数据观测

trace

方法内部调用路径,并输出方法路径上的每个节点上耗时

stack

输出当前方法被调用的调用路径

tt

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

reset

重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类

quit

退出当前 Arthas 客户端,其他 Arthas 客户端不受影响

shutdown

关闭 Arthas 服务端,所有 Arthas 客户端全部退出

dashboard

参数解释

ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应

NAME: 线程名

GROUP: 线程组名

PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高

STATE: 线程的状态

CPU%: 线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。

TIME: 线程运行总时间

INTERRUPTE: 线程当前的中断位状态

DAEMON: 是否是daemon线程

thread

参数说明

id

线程id

-n

指定最忙的前N个线程并打印堆栈

b

找出当前阻塞其他线程的线程

-i

指定cpu占比统计的采样间隔,单位为毫秒

PS: 这里的cpu统计的是,一段采样间隔内,当前JVM里各个线程所占用的cpu时间占总cpu时间的百分比。其计算方法为: 首先进行一次采样,获得所有线程的cpu的使用时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime这个接口),然后睡眠一段时间,默认100ms,可以通过-i参数指定,然后再采样一次,最后得出这段时间内各个线程消耗的cpu时间情况,最后算出百分比。注意: 这个统计也会产生一定的开销(JDK这个接口本身开销比较大),因此会看到as的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如5000毫秒。

sc

参数说明

class-pattern

类名表达式匹配

-d

输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。如果一个类被多个ClassLoader所加载,则会出现多次

-E

开启正则表达式匹配,默认为通配符匹配

-f

输出当前类的成员变量信息(需要配合参数-d一起使用)

-x

指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出

PS: class-pattern支持全限定名,如com.test.AAA,也支持com/test/AAA这样的格式,这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.啦,
sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true开关

sm

参数说明

class-pattern

类名表达式匹配

method-pattern

方法名表达式匹配

-d
展示每个方法的详细信息

-E
开启正则表达式匹配,默认为通配符匹配

PS:查看已加载类的方法信息, “Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到

monitor

monitor 命令是一个非实时返回命令,实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而被不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何 Arthas 的命令也不会引起任何原有业务逻辑的改变

参数说明

class-pattern

类名表达式匹配

method-pattern

方法名表达式匹配

-c
统计周期,默认值为120秒

监控项

timestamp 时间戳
class java类
method 方法(构造方法、普通方法)
total 调用次数
success 成功次数
fail 失败次数
rt 平均rt
fail-rate 失败率

PS:方法执行监控, 对匹配 class-pattern/method-pattern的类、方法的调用进行监控。

trace

方法内部调用路径,并输出方法路径上的每个节点上耗时, trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。

trace 能方便的帮助你定位和发现因 RT 高而导致的性能问题缺陷,但其每次只能跟踪一级方法的调用链路

trace 在执行的过程中本身是会有一定的性能开销,在统计的报告中并未像 JProfiler 一样预先减去其自身的统计开销。所以这统计出来有些许的不准,渲染路径上调用的类、方法越多,性能偏差越大。但还是能让你看清一些事情的。

参数说明

class-pattern

类名表达式匹配

method-pattern

方法名表达式匹配

condition-express

条件表达式

-n

命令执行次数

#cost

方法执行耗时

PS: 很多时候我们只想看到某个方法的rt大于某个时间之后的trace结果,例如trace *StringUtils isBlank '$cost>100'表示当执行时间超过100ms的时候,才会输出trace的结果。

stack

输出当前方法被调用的调用路径, 很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。

参数说明

class-pattern

类名表达式匹配

method-pattern

方法名表达式匹配

condition-express

条件表达式

-n

执行次数限制

watch

方法执行数据观测, 让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 groovy 表达式进行对应变量的查看。

参数说明

class-pattern

类名表达式匹配

method-pattern

方法名表达式匹配

express

观察表达式

condition-express

条件表达式

-b
在方法调用之前观察(默认关闭)

-e
在方法异常之后观察(默认关闭)

-s
在方法返回之后观察(默认关闭)

-f
在方法结束之后(正常返回和异常返回)观察 (默认开启)

-x
指定输出结果的属性遍历深度,默认为0

PS:这里重点要说明的是观察表达式,观察表达式的构成主要由 groovy 表达式组成,只要是一个合法的 groovy 表达式,都能被正常支持。

观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。

来源: https://segmentfault.com/a/1190000014618329

---------------------------------

Java 诊断利器-Arthas的基本使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/w57685321/article/details/84307516

简介

Arthas 是阿里巴巴最近才开源出来的一款 Java 诊断利器,它主要是针对线上环境,能够帮助我们更好的定位问题。
Case:https://github.com/alibaba/arthas/issues?q=label%3Auser-case
官方文档: https://alibaba.github.io/arthas
官方文档还是比较详细的,这里就挑几个觉得好用的来实操一下

实操

在这里插入图片描述
下载好后,目录是这样的,win运行bat脚本即可, as.bat +
在这里插入图片描述
pid可以通过jps命令找出来

package com.bfxy.springboot;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo {
    private static AtomicInteger count = new AtomicInteger(0);
    public static void increment() {
        count.incrementAndGet();
    }
    public static int value() {
        return count.get();
    }
    public static void main(String[] args) throws InterruptedException {
        while (true) {
            increment();
            System.out.println("counter: " + value());
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

然后写了个demo,attach上去
在这里插入图片描述
就到了启动界面

watch

方法执行数据观测
让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。
在这里插入图片描述
在这里插入图片描述
输入watch命令,监视Demo类中的value方法的返回值
在这里插入图片描述
发现跟这个是对应的,监控有效!
在这里插入图片描述
表达式使用”{params,returnObj}”,表示将入参和返回值打印出来,这里没有入参,result中的第一个@Object为空

trace

方法内部调用路径,并输出方法路径上的每个节点上耗时
trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
在这里插入图片描述
查看调用increment方法的链路耗时
在这里插入图片描述
也可以使用通配符监控所有方法

sc

查看JVM已加载的类信息
“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,这个命令支持的参数有 [d]、[E]、[f] 和 [x:]。

在这里插入图片描述
在这里插入图片描述
打印出了类的详细信息,看case里面,通过这种方法可以更好的判断NoSuchMethodError的原因

jad

反编译指定已加载类的源码
jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;

redefine

加载外部的.class文件,redefine jvm已加载的类。
注意, redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field),参考jdk本身的文档。

这个命令可以结合上面的jad命令一起使用,当需要调试正在运行的程序时,先jad反编译,然后修改了再redefine上去

redefine实战日志打印捕获

这也是根据里面的case学到的

背景:
随着应用越来越复杂,依赖越来越多,日志系统越来越混乱,有时会出现一些奇怪的日志,比如:
在这里插入图片描述
这个程序是一个短信发送程序,由于连接不上ISMG网关而报的错误,使用的短信开发包比较老,是直接在控制台打印的,都没有使用日志,有点坑,这样看起来就比较烦了,都不知道从哪里打印的这个,找源码又太慢,现在使用Arthas来解决它

在java代码里,字符串拼接基本都是通过StringBuilder来实现的,所以把StringBuilder复制下来,然后在toString方法中修改一下

    @Override
    public String toString() {
        // Create a copy, don't share the array
        String result = new String(value, 0, count);
        if(result.contains("Connection refused")) {
            System.err.println(result);
            new Throwable().printStackTrace();
        }
        return result;
    }

这里加上一个判断,为异常字符串中的就打印出堆栈
然后javac StringBuilder.java编译
在这里插入图片描述
还是attach上这个程序后,通过redefine命令去修改StringBuilder字节码
在这里插入图片描述
程序的运行也发生改变,这个功能还是很强大,都可以运行中动态调试程序了

 

-----

快速入门

1. 启动Demo

wget https://alibaba.github.io/arthas/arthas-demo.jar
java -jar arthas-demo.jar

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

arthas-demo源代码:

2. 启动arthas

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

wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
  • 执行该程序的用户需要和目标进程具有相同的权限。比如以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 arthas-demo.jar

Demo进程是第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://alibaba.github.io/arthas
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
$

3. 查看dashboard

输入dashboard,按回车/enter,会展示当前进程的信息,按ctrl+c可以中断执行。

$ dashboard
ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON
17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false
27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true
11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true
9 Attach Listener system 9 RUNNAB 0 0:0 false true
3 Finalizer system 8 WAITIN 0 0:0 false true
2 Reference Handler system 10 WAITIN 0 0:0 false true
4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true
26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true
13 job-timeout system 9 TIMED_ 0 0:0 false true
1 main main 5 TIMED_ 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false
18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false
23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166
ps_survivor_space 4M 5M 5M s)
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time( 0
code_cache 3M 5M 240M 1.32% ms)
Runtime
os.name Mac OS X
os.version 10.13.4
java.version 1.8.0_162
java.home /Library/Java/JavaVir
tualMachines/jdk1.8.0
_162.jdk/Contents/Hom
e/jre

4. 通过thread命令来获取到arthas-demo进程的Main Class

thread 1会打印线程ID 1的栈,通常是main函数的线程。

$ thread 1 | grep 'main('
at demo.MathGame.main(MathGame.java:17)

5. 通过jad来反编译Main Class

$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/arthas-demo.jar
/*
* 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();
private int illegalArgumentCount = 0;
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 {
try {
int number = random.nextInt();
List<Integer> primeFactors = this.primeFactors(number);
MathGame.print(number, primeFactors);
}
catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
}
}
public static void print(int number, List<Integer> primeFactors) {
StringBuffer sb = new StringBuffer("" + number + "=");
Iterator<Integer> iterator = primeFactors.iterator();
while (iterator.hasNext()) {
int factor = iterator.next();
sb.append(factor).append('*');
}
if (sb.charAt(sb.length() - 1) == '*') {
sb.deleteCharAt(sb.length() - 1);
}
System.out.println(sb);
}
public List<Integer> primeFactors(int number) {
if (number < 2) {
++this.illegalArgumentCount;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");
}
ArrayList<Integer> result = new ArrayList<Integer>();
int i = 2;
while (i <= number) {
if (number % i == 0) {
result.add(i);
number /= i;
i = 2;
continue;
}
++i;
}
return result;
}
}
Affect(row-cnt:1) cost in 970 ms.

6. watch

通过watch命令来查看demo.MathGame#primeFactors函数的返回值:

$ watch demo.MathGame primeFactors returnObj
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
@Integer[5],
@Integer[47],
@Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
@Integer[2],
@Integer[5],
@Integer[317],
@Integer[503],
@Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
@Integer[2],
@Integer[2],
@Integer[3],
@Integer[3],
@Integer[31],
@Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
@Integer[5],
@Integer[29],
@Integer[7651739],
]

更多的功能可以查看进阶使用

7. 退出arthas

如果只是退出当前的连接,可以用quit或者exit命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时可以直接连接上。

如果想完全退出arthas,可以执行shutdown命令。