前言
Arthas工具已经被我们项目组简单的应用到了物流项目的日常运维中。物流项目之前出现过生产消费速度不一致导致内存队列中的消息数据积压的问题,在后来解决了问题之后,我们项目组就更加重视了对JVM的日常监控,希望能借助工具及时的发现问题。这次把这篇文章归类到面试题中,是想表达这篇文章不是单纯的操作性文章,也会进行相关的原理分析,希望能对性能监控相关的面试有所帮助。
Arthas
Arthas能做什么?
就笔者的粗浅理解来说,Arthas主要能解决两个方面的问题:
- 线上debug
- 更方便的查看jvm相关的信息
Arthas的基本命令功能
dashboard
dashboard是一个上帝视角的数据监控面板,也是我们平时使用最多的功能,通过它可以监控线程/虚拟机和操作系统的基本信息,如下图:
一般来说,我们会监控Runnable线程都是哪些nio线程,数量代表了活跃一直在上数的物流门数量,还会看一下当前堆的大小,是否有内存泄漏的问题,还会看一下垃圾收集器的回收次数和单次回收时间,查看下是否有异常情况出现。
sc和sm
sc命令可以查看jvm已经加载的类信息。
可以使用-d -f输出更加详细的信息。
比如,我们想查看RabbitMQ的一个配置类是否被正确加载进来,就可以使用sc命令实现
[arthas@24222]$ sc *.RabbitConfig
cn.net.icomp.svcmw.config.RabbitConfig
cn.net.icomp.svcmw.config.RabbitConfig$$EnhancerBySpringCGLIB$$33a99423
可以查看到除了本来的类以外,还有一个spring用CGLIB加强的动态代理类
sm可以查看加载到元数据区的方法
jad
jad可以线上反编译class字节码,查问题利器!官方文档中介绍还可以配合mc和redefine热替换class,这个目前没有尝试过。
比如,可以使用jad反编译一个任务代码
方法监控相关
笔者认为方法监控相关的命令是很有意思的命令组,值得认真学习并应用到实际工作中。
注意:方法监控类相关的指令,原理是通过字节码增强的技术实现的,监控完毕后必须要调用stop(之前会自动reset)停止arthas或者reset重置加强过的类,否则可能会影响代码性能。
前置知识-OGNL表达式
参考资料:OGNL 语言介绍与实践
简单理解,OGNL就是一个可以提供对对象中的变量访问和设置的表达式。
watch-方法执行数据观测
watch主要用于观察 方法 的 入参 出参 返回值 和 异常等信息,还可以通过OGNL表达式对Arthas的通知对象中的其他相关变量进行查看。
所有可以观察的变量如下:
public class Advice {
private final ClassLoader loader;//调用类的类加载器
private final Class<?> clazz;//调用类的class对象
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;//方法是否正常返回
}
只观测方法进入时的入参,无返回值时,可以这样写
watch 类名表达式 方法名表达式 "{params}" -b -x 2
只观察方法异常时的出餐和返回值时,可以这样写
watch 类名表达式 方法名表达式 "{params,returnObj}" -e -x 2
只观察方法的正常返回时的出参和返回值时,可以这样写
watch 类名表达式 方法名表达式 "{params,returnObj}" -s -x 2
注意:
- 如果不加 -e -b -s的参数,默认是方法异常和正常返回的两种情况都会进行观察。
- -x 参数代表了遍历深度
- 还可以加 -n参数 指定执行的次数
- 还可以在观察表达式后面 加入额外的条件表达式,进一步过滤返回结果,比如:
- 按照cost耗时进行过滤 '#cost>200'
- 按照param的第一个参数是否小于0进行过滤 "params[0]<0"
trace命令-跟踪方法内部调用路径并统计耗时
原理文章:Trace命令的实现原理
简单总结下:
- trace匹配的是对应方法字节码中的方法调用指令(invokeXXX指令,只能从当前方法开始匹配第一层子方法,无法继续深入,如果想要监控方法调用链上的多层,需要用正则表达式手动匹配多个类和方法)
- 同一个线程中执行的所有方法按照调用顺序可建立一个树
代码实践:统计解码方法中耗时大于1ms的方法调用
[arthas@24222]$ trace *.ProtocolService decode '#cost > 1'
方法返回:
stack-统计当前方法的调用路径(了解当前方法都被哪些方法调用过)
其实统计的就是堆栈信息,和报异常打印的堆栈信息类似。
代码实战
统计decode方法的调用堆栈,只显示两条数据
[arthas@24222]$ stack *.ProtocolService decode -n 2
命令返回
tt-记录下每次方法调用的环境现场
tt存在的意义在于可以完整的记录针对当前方法的所有调用信息,包括每次调用的出入参(准确来说是出参,如果方法中对入参没有任何修改,那么出参=入参)返回值等等。
代码实战:
记录下decode方法的最近4次的方法调用,然后查看其中某一次方法调用。
[arthas@24222]$ tt -t *.ProtocolService decode -n 4
结果如下:
查看1004编号的这次方法调用。
小技巧:可以配合-x 2 把结果遍历深度设置为2(可以观察到返回值对象中属性的具体值)
monitor-方法执行统计(非实时方法)
monitor命令会给出一个周期内的某方法的统计信息,包括,调用次数成功次数失败次数,平均调用时间失败率等等。
代码实战:
每10秒统计一次,decode方法相关信息
[arthas@24222]$ monitor -c 10 *.ProtocolService decode
代码结果如下:
Arthas异常情况处理
端口被占用异常
如果观测了其中一个应用之后,使用exit指令关闭arthas,然后再使用下面指令启动arthas
java -jar /usr/local/arthas-packaging-3.2.0-bin/arthas-boot.jar
启动后会提示如下:
[root@rfidapp ~]# java -jar /usr/local/arthas-packaging-3.2.0-bin/arthas-boot.jar
[INFO] arthas-boot version: 3.2.0
[INFO] Process 10704 already using port 3658
[INFO] Process 10704 already using port 8563
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 10704 org.apache.storm.LogWriter
[2]: 17459 /data/sg/ledsend-0.0.1-SNAPSHOT.V2020071501jar
[3]: 10726 org.apache.storm.daemon.worker
[4]: 13594 org.apache.catalina.startup.Bootstrap
[5]: 24222 /data/sg/svc-mw-0.0.1-SNAPSHOT-V20200618.jar
[6]: 32015 org.apache.storm.daemon.supervisor.Supervisor
[7]: 25839 /data/sg/ledcalculate-0.0.1-SNAPSHOT.jar
如果选择了另外一个应用进行观测,就会报下面的错误
[ERROR] Target process 24222 is not the process using port 3658, you will connect to an unexpected process.
[ERROR] 1. Try to restart arthas-boot, select process 10704, shutdown it first with running the 'stop' command.
[ERROR] 2. Or try to use different telnet port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
这个提示表明,arthas的telnet端口和http端口已经被占用,如果要使用默认的端口,就需要关闭前一个观测的应用;要么就使用指定其他端口的方式来启动arthas,这两种方式都可以解决问题。