初探Arthas

Arthas能做什么?

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

  • 提供性能看板,包括线程、cpu、内存等信息,并且会定时的刷新。
  • 根据各种条件查看线程快照,比如找出cpu占用率最高的n个线程等。
  • 输出jvm的各种信息,如gc算法、gc次数及时间,jdk版本、classPath等
  • 查看JVM的系统属性(System Property)和当前JVM的环境属性(System Environment Variables)
  • 查看某个类的静态属性,也可以通过ognl表达式执行一些语句
  • 查看已加载的类的详细信息,比如这个类从哪个jar包加载的,也可以查看类的方法的信息
  • dump某个类的字节码到指定目录。
  • 直接反编译指定的类
  • 查看类加载器的一些信息。
  • 可以让jvm重新加载某个类
  • 监控方法的执行,同时可以获取到执行的入参、出参以及抛出的异常
  • 追踪方法执行的调用栈,以及各个方法的调用时间

......

快速入门

在线下载

Bash

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

运行程序

Bash

java -jar arthas-boot.jar

程序运行后,会显示出所有的java进程,在下面输入进程的序号Arthas就会attach到目标进程上,并输出日志,如下图ThreadBlockTest进程的编号是3,则输入3,再输入回车/enter即可 attach 到目标进程。

端口被占用问题处理:

java.exe -jar C:/Users/hongbo.liu/arthas-boot.jar --telnet-port 9991 --http-port -1

常用命令

基础命令

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

Jvm相关

  1. dashboard——查看当前系统的实时数据面板

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

数据说明

  • ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应。
  • NAME: 线程名
  • GROUP: 线程组名
  • PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
  • STATE: 线程的状态
  • CPU%: 线程的cpu使用率。比如采样间隔1000ms,某个线程的增量cpu时间为100ms,则cpu使用率=100/1000=10%
  • DELTA_TIME: 上次采样之后线程运行增量CPU时间,数据格式为秒
  • TIME: 线程运行总CPU时间,数据格式为分:秒
  • INTERRUPTED: 线程当前的中断位状态
  • DAEMON: 是否是daemon线程

JVM内部线程

  • java8之后支持获取JVM内部线程的cpu时间,ID为-1
  • JVM堆(heap)/方法区(matespace)内存不足或者OOM时,GC线程的cpu占用率明显高于其他线程。(GC task thread #6等)。
  • 垃圾回收频率和时间与我们选择的垃圾回收器有关:
  • 新生代使用的垃圾回收器如:Parallel Scavenge(吞吐量优先),它的目标是将用户线程的吞吐量达到一个可控制值,吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间)。高吞吐量则可用最高效率地利用CPU时间,提高计算速度。
  • CMS等垃圾回收器的关注点是尽可能地缩短垃圾回收时,用户线程的停顿时间。GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的:新生代空间调小会直接导致垃圾回收更频繁:如原来新生代500M时,老年代需要10秒收集一次、每次停顿100毫秒,现在变成300M后,5秒收集一次,每次停顿70毫秒。停顿时间的确在下降,但用户线程的吞吐量也降下来了。
  • 总之具体使用哪种垃圾回收器需要根据具体的需求决定停顿时间越短就越适合与用户频繁交互的程序,能够提高用户的使用体验;高吞吐量则可用最高效率地利用CPU时间,尽可能快地完成程序的计算任务,适合后台运算较多而不需要和用户交互的任务。
  • 当JVM热更新字节码时(执行redefine/trace/watch/tt),会清除class相关的JIT编译结果,需要重新编译,因此JIT线程使用率偏高。(C1 CompilerThread1等)
  1. thread——查看当前 JVM 的线程堆栈信息 。

同 jdk 自带的jstack命令,java常见命令及Java Dump的使用传送门:https://www.cnblogs.com/kongzhongqijing/articles/5534624.html

参数说明

-n

查看当前进程最忙碌的n个线程

-i

指定采样时间,单元为毫秒,默认200ms

-b

阻塞其他线程的线程(进行死锁的排查)

–state

查看指定状态的线程

thread -n 3 -i 5000 : 列出5000ms内最忙的3个线程栈。(使用cpu时间最多的3个线程)

ps:本例中起三个线程分别执行死循环操作。

数据说明

  • cpuUsage为采样间隔时间内线程的CPU使用率,与dashboard命令的数据一致。
  • 首先第一次采样,获取所有线程的CPU时间,然后睡眠等待一个间隔时间。
  • 再次第二次采样,获取所有线程的CPU时间,对比两次采样数据,计算出每个线程的增量CPU时间:线程CPU使用率 = 线程增量CPU时间 / 采样间隔时间 * 100%
  • deltaTime为采样间隔时间内线程的增量CPU时间,小于1ms时被取整显示为0ms。
  • time 线程运行总CPU时间

thead -i 5000 查看到默认按cpu增量时间递减显示第一页线程信息。

线程的几种状态说明:

TIMED_WA为线程调用了指定的正的等待时间(为参数)的以下方法中的其一:

  • Thread.sleep
  • 带时限(timeout)的 Object.wait
  • 带时限(timeout)的 Thread.join
  • LockSupport.parkNanos
  • LockSupport.parkUntil。

BLOCKED是指线程正在等待获取锁而阻塞。

WATTING是指线程调用wait()等方法主动让出cpu时间,等待其他线程唤醒(如:notify),被唤醒的线程可能继续向后执行(RUNNABLE),也可能需要再次进行锁竞争,进入BLOCKED状态。

thread -b 查看当前阻塞其他线程的线程。

可以看到测试demo中,多个线程抢占obj锁对象,其中有一个线程先拿到锁obj,导致其他线程处于等待锁的状态。

Java

new Thread(new Runnable() {

    public void run() {

        synchronized (obj) {

            try {

                Thread.sleep(300000);//示例代码中第24行

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

}).start();

使用thread命令我们可以看到,thread-0 调用sleep()方法导致thread-1处于等待获取锁的状态。

getstatic -c  classloader(hashcode)  class_name  field_name 指定类加载器,获取类的静态变量。(sc -d 类名取类加载器hashcode的方法)

Class相关

sc 查看JVM已加载的类信息

参数说明

-d

查看类的详细信息

-f

查看类的域(filed)信息

sc -d  -f  查看类的详细信息及其域信息(可通过.*的方式模糊匹配)

sm 查看已加载类的方法信息(只能看到由当前类所声明的方法,无法看到父类的方法)

-d

查看类的所有方法详细信息

class-pattern

类名匹配

method-pattern

  方法名匹配

sm -d  类路径  方法名 (不写方法名,查看当前类的所有方法,不写-d  只显示方法名)

jad --source-only 反编译只显示源代码

 classloader -t  获取classloader信息

retransform加载外部的class文件, 重新定义jvm已加载的类,实现jvm热更新。

说明:对象行为——>方法区——>字节码——>java源文件

java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行JVM上的应用程,实际上提供了一种虚拟机级别支持的 AOP 实现方式。java.lang.instrument.Instrumentation类提供retransformClasses方法,主要作用于对初始化过的clas重新转换,转换后的类文件字节作为类的新定义安装,实现方法区类定义的改变。

JVM热更新:

demo中启动一个springboot项目,在项目不做重启的情况下,实现jvm字节码热更新。

项目启动访问 curl -0 http://localhost:8080/user/10

此时想要修改业务代码(不能增删改类中的字段以及方法参数、方法名称及返回值,建议只做一些简单的修改,如日志打印)。

jad --source-only com.arthas.demo.controller.UserController  > D:/arthas/UserController.java

反编译代码到指定的java文件,然后修改业务代码(添加业务日志等)。

mc -c 5910e440  D:/arthas/UserController.java -d  D:/arthas/  编译修改过的代码

retransform  "D:\arthas\com\arthas\demo\controller\UserController.class"  热更新class

再次访问curl -0 http://localhost:8080/user/10 我们可以看到

说明:对象行为——>方法区——>字节码——>java源文件

java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行JVM上的应用程,实际上提供了一种虚拟机级别支持的 AOP 实现方式。java.lang.instrument.Instrumentation类提供retransformClasses方法,主要作用于对初始化过的clas重新转换,转换后的类文件字节作为类的新定义安装,实现方法区类定义的改变。

Java  热更新问题:

windows环境用java -jar xx启动的应用和arthas,成功attach。使用mc -c xx xx.java -d xx时报please confirm the application running in JDK not JRE。

解决方案:

1)将{jdk}/lib下的tool.jar文件复制到{jdk}/jre/lib

2)进入jre/bin目录,用java.exe -jar xx分别启动应用和arthas

如:

java.exe -jar C:\Users\hongbo.liu\arthas-boot.jar

java.exe -jar D:/arthas/arthas-0.0.1-SNAPSHOT.jar

字节码增强

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

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

 monitor -c 5 com.demo.arthas.MonitorTest  method1 以5秒为周期对method1进行监控

数据说明:

total 总共调用次数,success成功次数 fail失败次数 avg-rt 平均响应时长 fail-rate 失败占比

 monitor -b -c 5 com.demo.arthas.MonitorTest  method1 "i>30"  按条件表达式在方法结束前筛选(不带-b 则表示在方法执行结束后筛选)

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

参数说明

  • watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后。
  • 4个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
  • 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参
  • 当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在
  • 可指定在方法指定输出内容(params 参数 ,target对象信息,returnObj返回值,throwExp异常
  • 在watch命令的结果里,会打印出location信息。location有三种可能值:AtEnter,AtExit,AtExceptionExit。对应函数入口,函数正常return,函数抛出异常。
  • -x表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1。
  • -n 表示方法调用次数,避免循环调用刷屏。

我们可以根据指定方法的入参条件表达式和方法耗时条件进行过滤输出,如:下面示例中,只有方法的第一个参数值为‘ABC’且在抛出异常时才会输出。

watch  com.demo.arthas.WatchTest  method1 "{params,returnObj,throwExp}"  'params[0]=="ABC"' -e -x 2 

 耗时大于10ms并且第一个参数等于1才输出

watch  com.demo.arthas.WatchTest  method1 "{params,returnObj,throwExp}" '#cost>10 && params[0]=="ABC"' -x 5 -n 1

注意:同时指定了-s和-b,方法被调用一次,分别是方法被调用前,和返回之后注意,这里如果-n只设置成1,那么只会输出-b对应的输出,-s对应的输出由于没有次数了就无法输出。

watch  com.demo.arthas.WatchTest  method1   '{params,returnObj,throwExp}' -x 5 -n 2 -s -b

Trace 方法内部调用路径,并输出方法路径上的每个节点上耗时,能方便定位到 RT 高而导致的性能问题,但其每次只能跟踪一级方法的调用链路。(参考:Trace命令的实现原理

参数说明

参数

说明

class-pattern

类名表达式匹配

method-pattern

方法名表达式匹配

condition-express

条件表达式

-n

指定捕捉结果的次数(命令执行的次数)

#cost   

方法执行耗时(watch/stack/trace这个三个命令都支持#cost)

trace com.demo.arthas.TraceTest method1  

我们看到method1调用了method2,在method2耗时较高,可以使用#cost进行筛选。

 trace com.demo.arthas.TraceTest method1 '#cost > 1000'

注意:

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

race命令只会trace匹配到的函数里的子调用,并不会向下trace多层。因为trace是代价比较贵的,多层trace可能会导致最终要trace的类和函数非常多。

如果想要trace多层,可以再次调用trace到制定的方法,实现多层trace。

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

参数说明

参数

说明

class-pattern

类名表达式匹配

method-pattern

方法名表达式匹配

condition-express

条件表达式

-n

指定捕捉结果的次数(命令执行的次数)

特殊用法请参考:https://github.com/alibaba/arthas/issues/71

OGNL表达式官网:https://commons.apache.org/proper/commons-ognl/language-guide.html

track com.demo.arthas.StackTest method2 '#cost > 1000' (同时也可指定params入参条件来筛选方法 如 params[0]==1等)

 tt -t  com.demo.arthas.TtTest  method1

当你执行一个调用量不高的方法时,可以用 CTRL+C 中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。因此可以通过-n指定记录次数,Arthas 会主动中断tt命令的记录过程。

Arthas 很难区分出重载的方法

我们可以像watch、trace、stack 命令命令一样使用条件表达式进行区分(条件表达式也是用 OGNL 来编写,核心的判断对象依然是 Advice 对象,前边看到了很多条件表达式中,都使用了 params[0],有关这个变量的介绍,请参考官网文档表达式核心变量

解决方法重载

tt -t   com.demo.arthas.TtTest  method1  params.length==1

通过制定参数个数的形式解决不同的方法签名,如果参数个数一样,你还可以这样写

tt -t  com.demo.arthas.TtTest  method1  'params[1] instanceof String'

解决指定参数

tt -t  com.demo.arthas.TtTest  method1  params[0].adsensekey=="ABC"

总结:

以上是arthas常用命令使用方法的汇总,通过arthas可以帮助我们快速定位、分析诊断问题。

问题产生终归是有原因的,如果我们只知其一不知其二,很难从根本上解决问题。相反,如果我们对问题产生的底层原理有一定的了解的话,那么我们工作中可能会减少问题的出现。

例如:不合理的JVM参数设置(如:堆内存大小分配,垃圾回收器的选择等),导致OOM或者垃圾回收线程过多直接造成系统性能问题,再如,不合理的使用多线程以及过多的系统调用(如:频繁IO),导致cpu状态和线程上下文切换过于频繁,导致系统资源紧张或用户线程能够获取的cpu时间很少,导致程序运行性能下降,譬如以上问题,如果我们对jvm虚拟机有深入的了解,或者对操作系统如何分配系统资源以及如何进行线程的调度有深度的理解,就可能在很大程度上避免一些问题的产生。

相关文档

1,Arthas 用户文档:https://arthas.aliyun.com/

2,操作系统——进程、内存、IO管理:《操作系统》

3,深入理解java虚拟机——垃圾回收:https://www.cnblogs.com/czwbig/p/11127159.html

4,深入理解java Instrumentation:https://www.jianshu.com/p/5c62b71fd882

5,类加载机制:https://zhuanlan.zhihu.com/p/137857652

该文档使用了 产品需求文档  模板,点击立刻体验

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值