likes
comments
collection
share

使用Arthas高效排查线上问题:深入获取对象属性详情的技巧

作者站长头像
站长
· 阅读数 34

这里是小奏,觉得文章不错可以关注公众号小奏技术

背景

线上运行中的项目有时候出了问题,总觉得比如是某个对象的属性值是有问题的,想查看一下。

或者是想看看线上某个线程池的队列是不是堆积了很多任务。

一般排查方式最简单最常用的就是,添加log比如

log.info("xxx:{}",user.getName());

然后merge代码,发布,上线。

整套流程非常麻烦,查询完了可能还要再删除多余的log代码。

有些三方jar里面的对象甚至需要改源码,更麻烦。

有没有什么能动态查看JVM中的对象的属性值呢?

Arthas就可以做到

Arthas查看属性值

Arthas支持的功能很多,这里我们只专注如何查看线上运行JVM中对象中的属性值

vmtool命令

vmtool可以利用JVMTI接口,实现查询内存对象,强制 GC 等功能。

我们要查看一个对象的属性值,肯定得先获取这个对象。

vmtool --action getInstances --className java.lang.String --limit 10

通过上面的命令就可以获取JVM中该对象的实例。

比如我要查看RocketMQ client中的MQClientInstance对象中的scheduledExecutorService属性值

我先直接给完整命令

vmtool --action getInstances --className org.apache.rocketmq.client.impl.factory.MQClientInstance --limit 10 --express 'instances[0].getScheduledExecutorService()'

这里我解释下这个命令

  • --action getInstances 获取实例

  • --className org.apache.rocketmq.client.impl.factory.MQClientInstance 获取MQClientInstance对象

  • --limit 10 限制返回10个MQClientInstance对象,因为我们的JVM中运行的MQClientInstance对象有很多个,通过 --limit参数,可以限制返回值数量,避免获取超大数据时对 JVM 造成压力。默认值是 10个

  • --express 这里主要是代表后面拼接执行表达式,比如拿到了这10个对象之后要进行什么操作,后面拼接ognl语句

ognl相关的教程这里可以去官网学习更多ognl

  • 'instances[0].getScheduledExecutorService()' 这里就是ognl表达式,获取第一个MQClientInstance对象,然后调用getScheduledExecutorService方法

我们可以看看运行结果

@DelegatedScheduledExecutorService[
    e=@ScheduledThreadPoolExecutor[java.util.concurrent.ScheduledThreadPoolExecutor@55c50cdd[Running, pool size = 1, active threads = 0, queued tasks = 35, completed tasks = 16556]],
    e=@ScheduledThreadPoolExecutor[java.util.concurrent.ScheduledThreadPoolExecutor@55c50cdd[Running, pool size = 1, active threads = 0, queued tasks = 35, completed tasks = 16556]],
]

如果我们加上-x 2参数,就可以看到更详细的信息, -x表示对象展开的层次,默认是1

vmtool --action getInstances --className org.apache.rocketmq.client.impl.factory.MQClientInstance --limit 10 --express 'instances[0].getScheduledExecutorService()' -x 2
@ScheduledFutureTask[
                sequenceNumber=@Long[298],
                time=@Long[5279358271057691],
                period=@Long[3600000000000],
                outerTask=@ScheduledFutureTask[java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@6230ff0f[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@60b5e6e0[Wrapped task = com.alibaba.ttl.TtlRunnable - org.apache.rocketmq.common.stats.StatsItemSet$5@278dbfb7]]],
                heapIndex=@Integer[19],
                this$0=@ScheduledThreadPoolExecutor[java.util.concurrent.ScheduledThreadPoolExecutor@102c2157[Running, pool size = 1, active threads = 0, queued tasks = 35, completed tasks = 90878]],
                state=@Integer[0],
                NEW=@Integer[0],
                COMPLETING=@Integer[1],
                NORMAL=@Integer[2],
                EXCEPTIONAL=@Integer[3],
                CANCELLED=@Integer[4],
                INTERRUPTING=@Integer[5],
                INTERRUPTED=@Integer[6],
                callable=@RunnableAdapter[java.util.concurrent.Executors$RunnableAdapter@60b5e6e0[Wrapped task = com.alibaba.ttl.TtlRunnable - org.apache.rocketmq.common.stats.StatsItemSet$5@278dbfb7]],
                outcome=null,
                runner=null,
                waiters=null,
                STATE=@FieldInstanceReadWrite[java.lang.invoke.VarHandleInts$FieldInstanceReadWrite@37880ac0],
                RUNNER=@FieldInstanceReadWrite[java.lang.invoke.VarHandleObjects$FieldInstanceReadWrite@3586e1e8],
                WAITERS=@FieldInstanceReadWrite[java.lang.invoke.VarHandleObjects$FieldInstanceReadWrite@640aede4],
            ],
            @ScheduledFutureTask[
                sequenceNumber=@Long[311],
                time=@Long[5304558271103660],
                period=@Long[86400000000000],
                outerTask=@ScheduledFutureTask[java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@32ddce18[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@37aad457[Wrapped task = com.alibaba.ttl.TtlRunnable - org.apache.rocketmq.common.stats.StatsItemSet$6@7867a6a4]]],
                heapIndex=@Integer[20],
                this$0=@ScheduledThreadPoolExecutor[java.util.concurrent.ScheduledThreadPoolExecutor@102c2157[Running, pool size = 1, active threads = 0, queued tasks = 35, completed tasks = 90878]],
                state=@Integer[0],
                NEW=@Integer[0],
                COMPLETING=@Integer[1],
                NORMAL=@Integer[2],
                EXCEPTIONAL=@Integer[3],
                CANCELLED=@Integer[4],
                INTERRUPTING=@Integer[5],
                INTERRUPTED=@Integer[6],
                callable=@RunnableAdapter[java.util.concurrent.Executors$RunnableAdapter@37aad457[Wrapped task = com.alibaba.ttl.TtlRunnable - org.apache.rocketmq.common.stats.StatsItemSet$6@7867a6a4]],
                outcome=null,
                runner=null,
                waiters=null,
                STATE=@FieldInstanceReadWrite[java.lang.invoke.VarHandleInts$FieldInstanceReadWrite@37880ac0],
                RUNNER=@FieldInstanceReadWrite[java.lang.invoke.VarHandleObjects$FieldInstanceReadWrite@3586e1e8],
                WAITERS=@FieldInstanceReadWrite[java.lang.invoke.VarHandleObjects$FieldInstanceReadWrite@640aede4],
            ]

可以看到很详细。

但是如果要继续获取到Queue里面的信息就比较麻烦了。因为

  1. ScheduledExecutorService仅仅是一个简单的接口,没有queue相关的属性方法
  2. ScheduledExecutorService接口的实现类DelegatedScheduledExecutorService也不是一个public类,是一个默认访问级别的内部类

使用Arthas高效排查线上问题:深入获取对象属性详情的技巧

所以要获取queue就很麻烦。得写一堆反射相关的ognl表达式 类似

ognl -x 3 'targetClass=org.apache.rocketmq.client.impl.factory.MQClientInstance.class, targetClass.getDeclaredField("scheduledExecutorService").setAccessible(true), scheduledExecutorServiceField=targetClass.getDeclaredField("scheduledExecutorService"), scheduledExecutorServiceField.setAccessible(true), scheduledExecutorService=scheduledExecutorServiceField.get(targetObject), scheduledThreadPoolExecutor=@java.util.concurrent.ScheduledThreadPoolExecutor@class.cast(scheduledExecutorService), queue=scheduledThreadPoolExecutor.getQueue(), queue.toArray()'

比较麻烦,这里就没研究了。这个表达式不能直接使用,仅供参考

如果是获取BrokerFixedThreadPoolExecutor对象的任务就非常简单,使用

vmtool --action getInstances --className org.apache.rocketmq.broker.latency.BrokerFixedThreadPoolExecutor --limit 100 --express "instances[5].workQueue.{#this.runnable}" -x 2

因为BrokerFixedThreadPoolExecutor直接继承了ThreadPoolExecutor

总结

可以看到Arthas对线上问题排查非常有帮助,效率也很高,还是要好好学习的。如果项目是基于docker部署的,推荐把arthas.jar打到基础镜像中。

转载自:https://juejin.cn/post/7385778376275410985
评论
请登录