Arthas实战技术

413 阅读7分钟

1. 概述概述

Arthas(阿尔萨斯) 能做什么?

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

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

怎么做的?

Arthas 是基于 ASMJava Agent 技术实现的 Java 诊断利器。

ASM 是指一个 Java 字节码操作框架,用于动态生成或者增强 class。

采用 Attach API 方式的 Java Agent 是指在 JVM 启动后通过 Attach API 执行 agentmian 方法,利用 Instrumentation API 的 debug 和 profiler 能力。

  1. 快速入门

1. 准备案例代码

以下是一个简单的Java程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。代码 的内容不用理会这不是现在关注的点。

package demo;



import java.util.ArrayList;

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();

        //死循环,每过 1 秒调用 1 次下面的方法(不是开启一个线程)

        while (true) {

            game.run();

            TimeUnit.SECONDS.sleep(1);

        }

    }



    //打印质因数分解的结果

    public static void print(int number, List<Integer> primeFactors) {

        StringBuffer sb = new StringBuffer(number + "=");

        for (int factor : primeFactors) {

            sb.append(factor).append('*');

        }

        if (sb.charAt(sb.length() - 1) == '*') {

            sb.deleteCharAt(sb.length() - 1);

        }

        System.out.println(sb);

    }



    //分解质因数

    public void run() throws InterruptedException {

        try {

            //随机生成 1 万以内的整数

            int number = random.nextInt() / 10000;

            //调用方法进行质因数分解

            List<Integer> primeFactors = primeFactors(number);

            //打印结果

            print(number, primeFactors);



        } catch (Exception e) {

            System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());

        }

    }



    //计算number的质因数分解

    public List<Integer> primeFactors(int number) {

        //如果小于 2 ,则抛出异常,并且计数加 1

        if (number < 2) {

            illegalArgumentCount++;

            throw new IllegalArgumentException("number is: " + number + ", need >= 2");

        }

        //用于保存每个质数

        List<Integer> result = new ArrayList<Integer>();

        int i = 2;

        while (i <= number) { //如果i大于number就退出循环

            //能整除,则i为一个因数,number为整除的结果再继续从 2 开始

            if (number % i == 0) {

                result.add(i);

                number = number / i;

                i = 2;

            } else {

                i++;

            }

        }



        return result;

    }

}
  1. 启动项目
  2. 启动Arthas java -jar arthas-boot.jar
  3. 选择要粘附的进程
  4. 如果端口号被占用,也可以通过以下命令换成另一个端口号执行
  5. 支持通过浏览器连接 arthas (http://127.0.0.1:3658/)
java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
  1. 常用命令

  1. dashboard 仪表板

  • 第一部分是显示JVM中运行的所有线程:所在线程组,优先级,线程的状态,CPU的占用率,是否是后台进程等
  • 第二部分显示的JVM内存的使用情况
  • 第三部分是操作系统的一些信息和Java版本号

  1. 通过 thread 命令来获取到线程信息

  1. 通过 jad 来反编译类

jad demo.MathGame --source-only

  1. 通过watch命令来查看函数的入参和返回值

watch demo.MathGame primeFactors '{params,returnObj,throwExp}'  -n 5  -x 3 
  1. 退出 arthas

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

无法复制加载中的内容

  1. JVM相关排查

  1. dashboard 显示当前系统的实时数据面板

  • ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应
  • NAME: 线程名
  • GROUP: 线程组名
  • PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
  • STATE: 线程的状态
  • CPU%: 线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。
  • TIME: 线程运行总时间,数据格式为分:秒
  • INTERRUPTED: 线程当前的中断位状态
  • DAEMON: 是否是daemon线程
  1. thread 查看当前 JVM 的线程堆栈信息

# 展示当前最忙的前 3 个线程并打印堆栈 

thread -n 3

# 当没有参数时,显示所有线程的信息

thread

# 显示 1 号线程的运行堆栈

thread 1

# 找出当前阻塞其他线程的线程,有时候我们发现应用卡住了, 通常是由于某个线程拿住了某个锁, 并且其他线程都在等待这把锁造成的。 为了排查这类问题, arthas提供了thread -b, 一键找出死锁原因。

thread -b

# 指定采样时间间隔,每过1000毫秒采样,显示最占时间的3个线程 

thread -i 1000 -n 3

# 查看处于等待状态的线程 

thread --state WAITING

  1. jvm 查看当前 JVM 的信息

  • COUNT: JVM当前活跃的线程数
  • DAEMON-COUNT: JVM当前活跃的守护线程数
  • PEAK-COUNT: 从JVM启动开始曾经活着的最大线程数
  • STARTED-COUNT: 从JVM启动开始总共启动过的线程次数
  • DEADLOCK-COUNT: JVM当前死锁的线程数
  • MAX-FILE-DESCRIPTOR-COUNT:JVM进程最大可以打开的文件描述符数
  • OPEN-FILE-DESCRIPTOR-COUNT:JVM当前打开的文件描述符数
  1. vmoption查看和设置系统参数

# 查看所有的选项 

vmoption 

# 查看指定的选项 

vmoption PrintGCDetails 

# 更新指定的选项 

vmoption PrintGCDetails true
  1. 实战使用monitor/watch/trace/stack相关

  1. Monitor

  • monitor 命令是一个非实时返回命令,实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。
  • 作用是监视一个时间段中指定方法的执行次数,成功次数,失败次数,耗时等这些信息

无法复制加载中的内容

# 过 5 秒统计一次,统计类demo.MathGame中primeFactors方法

monitor -c 5 demo.MathGame primeFactors

  1. Watch

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

无法复制加载中的内容

# 观察demo.MathGame类中primeFactors方法出参和返回值,结果属性遍历深度为 2 。params表示所有参数数组,returnObject表示返回值 

watch demo.MathGame primeFactors "{params,returnObj}" -x 2



# 观察方法入参,对比前一个例子,返回值为空(事件点为方法执行前,因此获取不到返回值)

watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b



# 同时观察方法调用前和方法返回后,参数里-n 2,表示只执行两次。这里输出结果中,第一次输出的是方法调用前的观察表达式的结果,第二次输出的是方法返回后的表达式的结果params表示参数,target表示执行方法的对象,returnObject表示返回值 

watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2



# 观察当前对象中的属性,如果想查看方法运行前后,当前对象中的属性,可以使用target关键字,代表当前对象

watch demo.MathGame primeFactors 'target'



# 条件表达式的例子,输出第 1 参数小于的情况

watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"

  1. Trace

无法复制加载中的内容

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

trace demo.MathGame run



# 如果方法调用的次数很多,那么可以用-n参数指定捕捉结果的次数。比如下面的例子里,捕捉到一次调用就退出命令。

trace demo.MathGame run -n 1



# 默认情况下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数,需要显式设置--skipJDKMethod false。

trace --skipJDKMethod false demo.MathGame run



# 据调用耗时过滤,trace大于0.5ms的调用路径

trace demo.MathGame run '#cost > .5'

  1. Stack

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

无法复制加载中的内容

# 获取primeFactors的调用路径

stack demo.MathGame primeFactors



# 条件表达式来过滤,第 0 个参数的值小于 0 ,-n表示获取 2 次

stack demo.MathGame primeFactors 'params[0]<0' -n 2



# 据执行时间来过滤,耗时大于 5 毫秒 

stack demo.MathGame primeFactors '#cost>5'

  1. Profiler

  • profiler 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。
  • 命令基本运行结构是 profiler 命令 [命令参数]

无法复制加载中的内容

# 启动 profiler

profiler start



# 显示支持的事件

profiler list



# 停止 profiler 生成 svg 格式结果

profiler stop



# 默认情况下,结果文件是svg格式,如果想生成html格式,可以用--format参数指定:或者在--file参数里用文件名指名格式。比如--file /tmp/result.html 

profiler stop --format html
  • 火焰图是基于 perf 结果产生的SVG 图片,用来展示 CPU 的调用栈。

    • y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
  • x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。