Arthas:Java应用诊断利器

1、简介

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。开发人员可以使用 Arthas 在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。

1.1、Arthas 能为你做什么?

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

Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。


2、Arthas 安装

Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,建议启动 Arthas 时,和目标 Java 应用使用相同版本的 JDK。

Arthas 支持以下的一些安装方式,用户可以根据环境,选择合适的安装方式。

本地启动 Arthas 时,如果当前主机没有 Java 进程,Arthas 会直接退出,如果有 Java 进程,会提示选择需要 attach 的 Java 进程序号。

2.1、快速安装

快速安装的 Arthas,在执行命令时可能会需求联网下载资源,推荐在本地试用时使用。

2.1.1、使用 arthas-boot(试用推荐)

下载“arthas-boot.jar”,然后用 java -jar的方式启动:

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

打印帮助信息:

java -jar arthas-boot.jar -h

可以手动下载,如果网络比较慢,可以使用 aliyun 的镜像: java -jar arthas-boot.jar --repo-mirror aliyun --use-http

2.1.2、使用 as.sh

Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行执行即可:

curl -L https://arthas.aliyun.com/install.sh | sh

上述命令会下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。

直接在 shell 下面执行./as.sh,就会进入交互界面。

也可以执行./as.sh -h来获取更多参数信息。

2.2、全量安装

最新版本,点击下载:https://arthas.aliyun.com/download/latest_version?mirror=aliyun

解压后,在文件夹里有“arthas-boot.jar”,直接用java -jar的方式启动:

java -jar arthas-boot.jar

打印帮助信息:

java -jar arthas-boot.jar -h
2.2.1、通过 deb 来安装

在 releases 页面下载 deb 包: https://github.com/alibaba/arthas/releases

2.2.1.1、安装 deb
sudo dpkg -i arthas*.deb
2.2.1.2、deb 安装的用法
as.sh

2.3、手动安装

手动安装是使用已下载的压缩包执行本地安装的方法。推荐在服务器等联网受限的环境使用。

1.下载最新版本

最新版本,点击下载:https://arthas.aliyun.com/download/latest_version?mirror=aliyun

2.解压缩 arthas 的压缩包

unzip arthas-packaging-bin.zip

3.安装 Arthas

安装之前最好把所有老版本的 Arthas 全都删掉

sudo su javaer
rm -rf ~/.arthas/lib/*
cd arthas
./install-local.sh

注意,这里根据你需要诊断的 Java 进程的所属用户进行切换。

4.启动 Arthas

启动之前,请确保老版本的 Arthas 已经 stop。

./as.sh

2.4、卸载

在 Linux/Unix/Mac 平台,删除下面文件:

rm -rf ~/.arthas/
rm -rf ~/logs/arthas

Windows 平台直接删除 ”user home“ 下面的 .arthaslogs/arthas 目录


3、快速入门

3.1、启动 math-game

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

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

math-game 也存在于下载安装包中,如果使用手动安装,可以直接执行,无需下载。

3.2、启动 arthas

java -jar arthas-boot.jar
  • 执行该程序的用户需要和目标进程具有相同的权限。比如以 javaer 用户来执行:sudo su javaer && java -jar arthas-boot.jarsudo -u javaer -EH java -jar arthas-boot.jar
  • 如果 attach 不上目标进程,可以查看“~/logs/arthas/”目录下的日志。
  • java -jar arthas-boot.jar -h 打印更多参数信息。

输入序号,选择应用 java 进程:

Arthas script version: 3.7.2
[INFO] JAVA_HOME: /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home
Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 37928 math-game.jar

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

Arthas home: /arthas-packaging-3.7.2-bin
Calculating attach execution time...
Attaching to 37928 using version /arthas-packaging-3.7.2-bin...

real	0m1.166s
user	0m0.434s
sys	0m0.049s
Attach success.
telnet connecting to arthas server... current timestamp is 1713150720
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'

wiki       https://arthas.aliyun.com/doc
tutorials  https://arthas.aliyun.com/doc/arthas-tutorials.html
version    3.7.2
main_class demo.MathGame
pid        37928
time       2024-04-15 11:12:00

[arthas@37928]$

3.3、查看 dashboard

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

ID      NAME                                           GROUP                  PRIORITY        STATE          %CPU            DELTA_TIME     TIME            INTERRUPTED    DAEMON
-1      C2 CompilerThread0                             -                      -1              -              0.63            0.031          0:4.439         false          true
-1      C1 CompilerThread0                             -                      -1              -              0.35            0.017          0:1.445         false          true
94      Timer-for-arthas-dashboard-4ba5e31d-19db-48d6- system                 5               RUNNABLE       0.33            0.016          0:0.018         false          true
92      arthas-NettyHttpTelnetBootstrap-3-2            system                 5               RUNNABLE       0.13            0.006          0:0.072         false          true
-1      VM Periodic Task Thread                        -                      -1              -              0.07            0.003          0:2.219         false          true
-1      G1 Young RemSet Sampling                       -                      -1              -              0.03            0.001          0:0.749         false          true
1       main                                           main                   5               TIMED_WAITING  0.02            0.001          0:0.879         false          false
-1      VM Thread                                      -                      -1              -              0.01            0.000          0:0.138         false          true
85      Keep-Alive-Timer                               InnocuousThreadGroup   8               TIMED_WAITING  0.0             0.000          0:0.000         false          true
-1      GC Thread#1                                    -                      -1              -              0.0             0.000          0:0.045         false          true
-1      GC Thread#2                                    -                      -1              -              0.0             0.000          0:0.045         false          true
-1      GC Thread#3                                    -                      -1              -              0.0             0.000          0:0.046         false          true
-1      GC Thread#0                                    -                      -1              -              0.0             0.000          0:0.046         false          true
2       Reference Handler                              system                 10              RUNNABLE       0.0             0.000          0:0.000         false          true
3       Finalizer                                      system                 8               WAITING        0.0             0.000          0:0.000         false          true
4       Signal Dispatcher                              system                 9               RUNNABLE       0.0             0.000          0:0.000         false          true
11      Attach Listener                                system                 9               RUNNABLE       0.0             0.000          0:0.042         false          true
83      arthas-timer                                   system                 9               WAITING        0.0             0.000          0:0.000         false          true
86      arthas-NettyHttpTelnetBootstrap-3-1            system                 5               RUNNABLE       0.0             0.000          0:0.015         false          true
Memory                                  used          total        max           usage        GC
heap                                    90M           256M         4096M         2.20%        gc.g1_young_generation.count                   6
g1_eden_space                           48M           111M         -1            43.24%       gc.g1_young_generation.time(ms)                39
g1_old_gen                              31M           134M         4096M         0.76%        gc.g1_old_generation.count                     0
g1_survivor_space                       11M           11M          -1            100.00%      gc.g1_old_generation.time(ms)                  0
nonheap                                 57M           60M          -1            94.18%
codeheap_'non-nmethods'                 1M            2M           5M            21.62%
metaspace                               42M           43M          -1            96.86%
codeheap_'profiled_nmethods'            7M            7M           117M          6.14%
compressed_class_space                  4M            5M           1024M         0.48%
codeheap_'non-profiled_nmethods'        1M            2M           117M          1.60%
mapped                                  0K            0K           -             0.00%
direct                                  12M           12M          -             100.00%
Runtime
os.name                                                                                       Mac OS X
os.version                                                                                    10.15.7
java.version                                                                                  11.0.18
java.home                                                                                     /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home
systemload.average                                                                            2.39
processors                                                                                    4
# timestamp/uptime                                                                              Mon Apr 15 13:15:24 CST 2024/3462s

在这里插入图片描述

3.4、通过 thread 命令来获取到math-game进程的 Main Class

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

$ thread 1
"main" Id=1 TIMED_WAITING
    at java.base@11.0.18/java.lang.Thread.sleep(Native Method)
    at java.base@11.0.18/java.lang.Thread.sleep(Thread.java:334)
    at java.base@11.0.18/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
    at app//demo.MathGame.main(MathGame.java:17)

3.5、通过 jad 来反编译 Main Class

$ jad demo.MathGame

ClassLoader:
+-jdk.internal.loader.ClassLoaders$AppClassLoader@277050dc
  +-jdk.internal.loader.ClassLoaders$PlatformClassLoader@bcdf432

Location:
/arthas-packaging-3.7.2-bin/math-game.jar

       /*
        * Decompiled with CFR.
        */
       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 List<Integer> primeFactors(int number) {
/*44*/         if (number < 2) {
/*45*/             ++this.illegalArgumentCount;
                   throw new IllegalArgumentException("number is: " + number + ", need >= 2");
               }
               ArrayList<Integer> result = new ArrayList<Integer>();
/*50*/         int i = 2;
/*51*/         while (i <= number) {
/*52*/             if (number % i == 0) {
/*53*/                 result.add(i);
/*54*/                 number /= i;
/*55*/                 i = 2;
                       continue;
                   }
/*57*/             ++i;
               }
/*61*/         return result;
           }

           public static void main(String[] args) throws InterruptedException {
               MathGame game = new MathGame();
               while (true) {
/*16*/             game.run();
/*17*/             TimeUnit.SECONDS.sleep(1L);
               }
           }

           public void run() throws InterruptedException {
               try {
/*23*/             int number = random.nextInt() / 10000;
/*24*/             List<Integer> primeFactors = this.primeFactors(number);
/*25*/             MathGame.print(number, primeFactors);
               }
               catch (Exception e) {
/*28*/             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 + "=");
/*34*/         for (int factor : primeFactors) {
/*35*/             sb.append(factor).append('*');
               }
/*37*/         if (sb.charAt(sb.length() - 1) == '*') {
/*38*/             sb.deleteCharAt(sb.length() - 1);
               }
/*40*/         System.out.println(sb);
           }
       }

Affect(row-cnt:1) cost in 746 ms.

3.6、watch

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

$ watch demo.MathGame primeFactors returnObj
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 148 ms, listenerId: 1
method=demo.MathGame.primeFactors location=AtExit
ts=2024-04-15 13:23:13; [cost=3.269941ms] result=@ArrayList[
    @Integer[7],
    @Integer[17789],
]
method=demo.MathGame.primeFactors location=AtExit
ts=2024-04-15 13:23:14; [cost=0.057208ms] result=@ArrayList[
    @Integer[3],
    @Integer[3],
    @Integer[5],
    @Integer[5],
    @Integer[11],
]
method=demo.MathGame.primeFactors location=AtExit
ts=2024-04-15 13:23:15; [cost=1.968912ms] result=@ArrayList[
    @Integer[3],
    @Integer[66071],
]

3.7、退出 arthas

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

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


4、表达式核心变量

Arthas 的命令参数中经常会出现“匹配表达式”、“观察表达式” 概念, 无论是匹配表达式也好、观察表达式也罢,他们核心判断变量都是围绕着一个 Arthas 中的通用通知对象 Advice 进行。

它的简略代码结构如下:

public class Advice {

    private final ClassLoader loader;
    private final Class<?> clazz;
    private final ArthasMethod method;
    private final Object target;
    private final Object[] params;
    private final Object returnObj;
    private final Throwable throwExp;
    private final boolean isBefore;
    private final boolean isThrow;
    private final boolean isReturn;

    // getter/setter
}

这里列一个表格来说明不同变量的含义:

变量名变量解释
loader本次调用类所在的 ClassLoader
clazz本次调用类的 Class 引用
method本次调用方法反射引用
target本次调用类的实例
params本次调用参数列表,这是一个数组,如果方法是无参方法则为空数组
returnObj本次调用返回的对象。当且仅当 isReturn==true 成立时候有效,表明方法调用是以正常返回的方式结束。如果当前方法无返回值 void,则值为 “null”
throwExp本次调用抛出的异常。当且仅当 isThrow==true 成立时有效,表明方法调用是以抛出异常的方式结束。
isBefore辅助判断标记,当前的通知节点有可能是在方法一开始就通知,此时 isBefore==true 成立,同时 isThrow==falseisReturn==false,因为在方法刚开始时,还无法确定方法调用将会如何结束。
isThrow辅助判断标记,当前的方法调用以抛异常的形式结束。
isReturn辅助判断标记,当前的方法调用以正常返回的形式结束。

所有变量都可以在表达式中直接使用,如果在表达式中编写了不符合 OGNL 脚本语法或者引入了不在表格中的变量,则退出命令的执行;用户可以根据当前的异常信息修正条件表达式或观察表达式


5、命令列表

5.1、基础命令

  • base64:base64 编码转换,和 linux 里的 base64 命令类似
  • cat:打印文件内容,和 linux 里的 cat 命令类似
  • cls:清空当前屏幕区域
  • echo:打印参数,和 linux 里的 echo 命令类似
  • grep:匹配查找,和 linux 里的 grep 命令类似
  • help:查看命令帮助信息
  • history:打印命令历史
  • keymap:Arthas 快捷键列表及自定义快捷键
  • pwd:返回当前的工作目录,和 linux 命令类似
  • quit:退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
  • reset:重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
  • session:查看当前会话的信息
  • stop:关闭 Arthas 服务端,所有 Arthas 客户端全部退出
  • tee:复制标准输入到标准输出和指定的文件,和 linux 里的 tee 命令类似
  • version:输出当前目标 Java 进程所加载的 Arthas 版本号

5.2、JVM 相关

  • dashboard:当前系统的实时数据面板
  • getstatic:查看类的静态属性
  • heapdump:dump java heap, 类似 jmap 命令的 heap dump 功能
  • jvm:查看当前 JVM 的信息
  • logger:查看和修改 logger
  • mbean:查看 Mbean 的信息
  • memory:查看 JVM 的内存信息
  • ognl:执行 ognl 表达式
  • perfcounter:查看当前 JVM 的 Perf Counter 信息
  • sysenv:查看 JVM 的环境变量
  • sysprop:查看和修改 JVM 的系统属性
  • thread:查看当前 JVM 的线程堆栈信息
  • vmoption:查看和修改 JVM 里诊断相关的 option
  • vmtool:从 jvm 里查询对象,执行 forceGc

5.3、class/classloader 相关

  • classloader:查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource
  • dump:dump 已加载类的 byte code 到特定目录
  • jad:反编译指定已加载类的源码
  • mc:内存编译器,内存编译.java文件为.class文件
  • redefine:加载外部的.class文件,redefine 到 JVM 里
  • retransform:加载外部的.class文件,retransform 到 JVM 里
  • sc:查看 JVM 已加载的类信息
  • sm:查看已加载类的方法信息

5.4、monitor/watch/trace 相关

请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 stop 或将增强过的类执行 reset 命令。

  • monitor:方法执行监控
  • stack:输出当前方法被调用的调用路径
  • trace:方法内部调用路径,并输出方法路径上的每个节点上耗时
  • tt:方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
  • watch:方法执行数据观测

5.5、profiler/火焰图

  • profiler:使用async-profiler对应用采样,生成火焰图
  • jfr:动态开启关闭 JFR 记录

async-profiler 项目是一个低开销的 Java 采样分析器,不受安全点偏差问题的影响。用于收集堆栈跟踪和跟踪内存分配。该分析器与 OpenJDK 和其他基于 HotSpot JVM 的 Java 运行时一起工作。

Async-profiler 可以跟踪以下类型的事件:

  • CPU周期
  • 硬件和软件性能计数器,如缓存错误,分支错误,页面错误,上下文切换等。
  • Java堆中的分配
  • 满足的锁尝试,包括 Java 对象监视器和 ReentrantLocks

5.6、鉴权

  • auth:鉴权,启动 Arthas 的时候可以使用--username--password设置用户名和密码,

5.7、auth

在启动 Arthas 的时候可以使用时,可以在命令行指定密码。比如:

java -jar arthas-boot.jar --password ppp
  • 可以通过--username选项来指定用户,默认值是“arthas”。
  • 也可以在 arthas.properties 里中配置 username/password。命令行的优先级大于配置文件。
  • 如果只配置 username,没有配置 password,则会生成随机密码,打印在 ~/logs/arthas/arthas.log

5.8、options

  • options:查看或设置 Arthas 全局开关

5.9、管道

Arthas 支持使用管道对上述命令的结果进行进一步的处理,如sm java.lang.String * | grep 'index'

  • grep:搜索满足条件的结果
  • plaintext:将命令的结果去除 ANSI 颜色
  • wc:按行统计输出结果

5.10、后台异步任务

当线上出现偶发的问题,比如需要 watch 某个条件,而这个条件一天可能才会出现一次时,异步后台任务就派上用场了

  • 使用 > 将结果重写向到日志文件,使用 & 指定命令是后台运行,session 断开不影响任务执行(生命周期默认为 1 天)
  • jobs:列出所有 job
  • kill:强制终止任务
  • fg:将暂停的任务拉到前台执行
  • bg:将暂停的任务放到后台执行

6、其他特性

6.1、Web Console

Arthas 目前支持 Web Console,用户在 attach 成功之后,可以直接访问:http://127.0.0.1:8563/。

可以填入 IP,远程连接其它机器上的 arthas。

在这里插入图片描述

默认情况下,arthas 只 listen 127.0.0.1,所以如果想从远程连接,则可以使用 --target-ip 参数指定 listen 的 IP,更多参考-h的帮助说明。 注意会有安全风险,考虑下面的 tunnel server 的方案。

默认 Web Console 支持向上回滚的行数是 1000。可以在 URL 里用scrollback指定。比如

http://127.0.0.1:8563/?scrollback=3000

6.2、启动应用时增加 Java Agent

通常 Arthas 是以动态 attach 的方式来诊断应用,但从3.2.0版本起,Arthas 支持直接以 java agent 的方式启动。

比如下载全量的 arthas zip 包,解压之后以 -javaagent 的参数指定 arthas-agent.jar 来启动:

java -javaagent:/path/to/arthas-agent.jar -jar math-game.jar

默认的配置项在解压目录里的arthas.properties文件里。

6.3、Arthas Tunnel

通过 Arthas Tunnel Server/Client 来远程管理/连接多个 Agent。

比如,在流式计算里,Java 进程可以是在不同的机器启动的,想要使用 Arthas 去诊断会比较麻烦,因为用户通常没有机器的权限,即使登陆机器也分不清是哪个 Java 进程。

在这种情况下,可以使用 Arthas Tunnel Server/Client。

6.3.1、下载部署 arthas tunnel server
  • 从 Maven 仓库下载:https://arthas.aliyun.com/download/arthas-tunnel-server/latest_version?mirror=aliyun
  • 从 Github Releases 页下载: https://github.com/alibaba/arthas/releases

Arthas tunnel server 是一个 spring boot fat jar 应用,直接java -jar启动:

java -jar  arthas-tunnel-server.jar

默认情况下,arthas tunnel server 的 web 端口是 8080,arthas agent 连接的端口是7777。

启动之后,可以访问 http://127.0.0.1:8080/ ,再通过agentId连接到已注册的 arthas agent 上。

通过 Spring Boot 的 Endpoint,可以查看到具体的连接信息: http://127.0.0.1:8080/actuator/arthas ,登陆用户名是 arthas,密码在 arthas tunnel server 的日志里可以找到。

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值