草庐IT

Arthas 使用详解

小码农叔叔 2023-10-25 原文

前言

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

Arthas 能做什么?

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

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

总的来说,Arthas 的出现可以大大提升程序员对线上问题的排查、定位,从而达到快速解决问题的目的,更加详细的介绍可以参考:https://arthas.aliyun.com/

由于涉及到的内容比较多,本篇将从如下几个方面进行介绍和说明:

  1. Arthas 快速安装与使用;
  2. Arthas 常用命令以及使用;
  3. Arthas  分析问题案例介绍;

一、Arthas 快速安装与使用

1、下载安装包

安装包下载

全量下载的包为一个.zip结尾的压缩包文件

 也可以在服务器下使用下面的命令进行下载

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

得到如下的一个springboot的jar包

2、解压包并上传arthas包和demo的jar包

3、启动math-game.jar 和arthas-boot.jar 

启动 math-game.jar

 启动 arthas-boot.jar

 这时候,arthas会列出当前检测到的java进程,需要你手动选择某个进程,这里选择1,并按回车键即可;

出现上面的界面,表示 arthas 已经黏附到当前的这个进程上面去了,接下来就可以开始你的arthas学习使用之旅了;

二、Arthas 常用命令以及使用

Arthas 官方提供了非常多的命令,用于生产环境的问题排查分析,下面列举一些常用的做详细的说明

1、dashboard

1)输入dashboard(仪表板),按 回车/enter ,会展示当前进程的信息,按 ctrl+c 可以中断执行;

2) dashboard命令可以查看cpu、线程状态,内存信息、GC情况和jdk版本等信息;

关于上面dashboard展示信息说明

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

由于监控页面会实时刷新,默认每5000毫秒(5秒)刷新一次。可以通过 - i 参数指定刷新频率,-n 参数指定刷新次数。这个统计会有一定的开销,从截图中也可以看到arthas的cpu占比比较大,所以刷新频率不要太高,建议5秒以上,刷新次数建议10次以内;
// 每10秒刷新一次,3次后停止 
dashboard -i 10000 -n 3 

2、thread

使用thread命令可以查看线程的状态,显示的结果其实就是dashboard结果的第一栏

我们在线上排查问题的时候,往往需要查看进程中某个或者几个最占资源的线程,就可以使用thread命令进行着手分析,比如,上图中,我们需要分析arthas-demo 中的main线程,可以使用 thread + 线程编号 进行输出;

thread命令可以追加的参数

  • id:可以查看指定线程id的堆栈信息;
  • -n value:找出最忙的value个线程,并打印堆栈信息;
  • -b:找出当前正在阻塞其他线程的线程;
  • -i value:指定采样cpu占比的时间间隔,默认为100ms;

打印当前最忙的3个线程的堆栈信息

打印正在阻塞其他线程的线程

3、jad

通过jad来反编译Main Class,生产环境下,当大致定位到问题时,想要进一步知道到底是哪一块的代码引起的问题时,不需要通过反编译工具下载jar包,直接通过jad命令,也可以将问题的代码反编译出来,如上面的demo的main class代码

4、watch

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

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

5、jvm

用于查看当前 JVM 的信息  

在jvm展示的信息列表中,通常比较关注 thread相关的信息

thread展示的核心参数说明:

  • COUNT: JVM当前活跃的线程数;
  • DAEMON-COUNT: JVM当前活跃的守护线程数 ;
  • PEAK-COUNT: 从JVM启动开始曾经活着的最大线程数;
  • STARTED-COUNT: 从JVM启动开始总共启动过的线程次数;
  • DEADLOCK-COUNT: JVM当前死锁的线程数;

6、sysprop

查看和修改当前 JVM 的系统属性( System Property)

查看所有属性

 查看单个属性,支持通过tab补全

7、vmoption

查看,更新VM诊断相关的参数(比如展示出来我们熟悉的JVM相关的参数和命令)

通过这个命令,排查问题的时候,比如遇到需要排查GC或者JVM参数设置相关的问题时,就可以通过该命令进行分析和定位;

查看所有的选项

查看指定的选项

更新指定的选项

8、dump

将已加载类的字节码文件保存到特定目录:logs/arthas/classdump/

参数说明:

数名称
参数说明
class-pattern
类名表达式匹配
[c:]
类所属 ClassLoader 的 hashcode
[E]
开启正则表达式匹配,默认为通配符匹配
举例
把上面的demo包下的MathGame类的字节码文件保存到 ~/logs/arthas/classdump/ 目录下:
dump demo.MathGame ~/logs/arthas/classdump/

 执行完成后,在目录下可以看到相关dump后的文件

9、monitor

方法执行监控对匹配 class-pattern method-pattern 的类、方法的调用进行监控

monitor 命令是一个非实时返回的命令,实时返回命令是输入之后立即返回,而非实时返回的命
令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。
参数说明

参数名称
参数说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
[E]
开启正则表达式匹配,默认为通配符匹配
[c:]
统计周期,默认值为120秒

 5秒统计一次,统计类demo.MathGameprimeFactors方法

monitor -c 5 demo.MathGame primeFactors

上面的截图中,展示出了其监控的各个维度的信息,具体说明如下:

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

10、trace

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

  • trace 命令能主动搜索 class-pattern / method-pattern 对应的方法调用路径,渲染和统计整 个调用链路上的所有性能开销和追踪调用链路;
  • 观察表达式的构成主要由ognl 表达式组成,所以你可以这样写 "{params,returnObj}" ,只要是 一个合法的 ognl 表达式,都能被正常支持;
  • 很多时候我们只想看到某个方法的rt大于某个时间之后的trace结果,现在Arthas可以按照方法执行的耗时来进行过滤了,例如 trace *StringUtils isBlank '#cost>100' 表示当执行时间超过100ms的时候,才会输出trace的结果;
  • watch/stack/trace这个三个命令都支持 #cost;
核心参数说明:
参数名
参数说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
condition-express
条件表达式
[E]
开启正则表达式匹配,默认为通配符匹配
[n:]
命令执行次数
#cost
方法执行耗时

1、trace函数指定类的指定方法

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

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

2、据调用耗时过滤,trace大于0.5ms的调用路径
trace demo.MathGame run '#cost > .5'

可以用正则表匹配路径上的多个类和函数,一定程度上达到多层 trace 的效果:
trace -E com.test.ClassA|org.test.ClassB method1|method2|method3

11、stack

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

参数说明:

参数名称
参数说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
condition-express
条件表达式
[E]
开启正则表达式匹配,默认为通配符匹配
[n:]
执行次数限制

1、获取primeFactors的调用路径

stack demo.MathGame primeFactors

补充说明:

条件表达式来过滤,第 0 个参数的值小于 0 -n 表示获取 2
stack demo.MathGame primeFactors 'params[0]<0' -n 2

 

2、按照执行时间过滤,耗时大于5毫秒

stack demo.MathGame primeFactors '#cost>5'

三、Arthas 分析问题案例

1、线程死锁

看下面这段线程死锁的代码

public class ThreaLockTest {

    public static String obj1 = "obj1";
    public static String obj2 = "obj2";
    public static void main(String[] args) {
        LockA la = new LockA(); //锁A
        new Thread(la).start();
        LockB lb = new LockB(); //锁B
        new Thread(lb).start();
    }
}

class LockA implements Runnable{
    public void run() {
        try { //异常捕获
            System.out.println(" LockA 开始执行");
            while(true){
                synchronized (ThreaLockTest.obj1) {
                    System.out.println(" LockA 锁住 obj1");
                    Thread.sleep(3000); // 此处等待是给B能锁住机会
                    synchronized (ThreaLockTest.obj2) {
                        System.out.println(" LockA 锁住 obj2");
                        Thread.sleep(60 * 1000); //占据资源
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class LockB implements Runnable{
    public void run() {
        try {
            System.out.println(" LockB 开始执行");
            while(true){
                synchronized (ThreaLockTest.obj2) {
                    System.out.println(" LockB 锁住 obj2");
                    Thread.sleep(3000); //给A能锁住机会
                    synchronized (ThreaLockTest.obj1) {
                        System.out.println(" LockB 锁住 obj1");
                        Thread.sleep(60 * 1000); //占据资源
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行上面的代码,效果如下:

使用arthas 排查流程

1)启动Arthas  jar包,并黏附当前的这个死锁的进程 

2)使用dashboard 命令定位当前的进程的整体情况

重点关注线程的状态,使用Arthas  的一点好处在于它清晰的罗列了线程的常见状态,比如这里死锁了,就展示出了 "BLOCKED" 状态,就能快速定位到死锁的线程;

3)使用 thread命令查询当前进程情况

4)使用jad命令反编译代码

通过第三步了解了死锁的类,就可以使用jad命令反编译一下代码,进一步定位造成死锁的代码块

那么通过上面几步的操作,定位到了造成线程死锁的代码的位置,就可以针对性的对代码进行优化和改进了;

2、CPU飙升问题

有如下代码

public class CpuHighTest {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(30 * 60 * 1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
            });
            thread.setName("thread-" + i);
            thread.start();
        }

        Thread highCpuThread = new Thread(() -> {
            int i = 0;
            while (true) {
                i++;
            }
        });
        highCpuThread.setName("HighCpu");
        highCpuThread.start();
    }

}

运行这段程序后,前面 10 个线程都处于休眠状态,只有最后一个线程会持续的占用 CPU ,接下来我们使用Arthas  来定位问题;

1)启动Arthas  jar包,并黏附当前的这个进程 

2)使用dashboard 命令定位当前的进程的整体情况

可以看到这里有一个22的线程的CPU占用非常高,同时Arthas  直接将对应的类名展现出来了

3)使用 thread命令查询当前进程情况

进一步定位到是该类下的 main方法中出现的问题 

4)使用jad命令反编译代码

通过jad命令定位出当前的问题代码,接下来就可以进一步分析和优化代码了

3、接口响应超时问题

在生产环境下,随着时间的增长或业务规模的提升,发现某些接口的响应越来越慢,这时候就需要排查接口响应慢,甚至的超时的问题;

有如下代码,模拟一个超时的接口

    @Autowired
    private OrderService orderService;

    //localhost:8089/create
    @GetMapping("/create")
    public String createOrder(){
        return orderService.createOrder();

    }

业务层

@Service
public class OrderService {

    public String createOrder() {
        System.out.println("开始执行业务");
        handleUser();
        handleOrder();
        handleStock();
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "订单创建成功";

    }

    public static void handleUser(){
        System.out.println("开始处理用户业务");
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("用户业务处理完毕");
    }

    public static void handleOrder(){
        System.out.println("开始处理订单业务");
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单业务处理完毕");
    }

    public static void handleStock(){
        System.out.println("开始处理库存业务");
        try {
            Thread.sleep(40000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("库存业务处理完毕");
    }

}

将代码运行起来,并通过浏览器调用一下接口:http://localhost:8089/create,模拟接口长时间无响应的效果,接下来用 Arthas  来定位问题;

1)启动Arthas  并黏附当前的进程

2)使用dashboard 命令定位当前的进程的整体情况

在这一栏中,要重点关注这个 state 一栏,这里面显示出了进程中可能存在问题的线程

3)使用 thread命令查询当前进程情况

通过thread命令列出了该线程中的那个类的哪个方法处理的非常慢,到这里,就大致定位到了问题的接口和方法;

4)使用 trace 命令进一步观察当前的方法调用链路中的各阶段执行耗时情况

trace  com.congge.web.MyController createOrder

在使用trace之后,根据链路中的各个阶段的执行耗时,进一步分析和定位到具体的代码块

一个真实的案例

小编在日常开发中,正好遇到了一个接口响应慢的问题,这里给出大致的分析过程,业务操作如下图,通过F12可以定位到具体的接口

然后通过Arthas,定位到该类的接口地址,使用trace命令追踪到接口中的方法,最终追踪并定位到在 parseXXX的方法中的某个sql有一定的性能问题引发的

本篇使用较大的篇幅介绍了 Arthas的使用,希望对您有用,本篇到此结束,最后感谢观看!

有关Arthas 使用详解的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐