arthas学习

2019-02-15  本文已影响1人  icecrea

启动arthas:
wget https://alibaba.github.io/arthas/arthas-boot.jar
使用aliyun的镜像:java -jar arthas-boot.jar --repo-mirror aliyun --use-http

命令:
查看dashboard

通过thread命令来获取到arthas-demo进程的Main Class
thread 1会打印线程ID 1的栈,通常是main函数的线程。

$ thread 1 | grep 'main('
    at demo.MathGame.main(MathGame.java:17)

通过jad来反编绎Main Class
$ jad demo.MathGame

watch命令

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

$ watch demo.MathGame primeFactors returnObj
观察方法出参和返回值
$ watch demo.MathGame primeFactors "{params,returnObj}" -x 2
观察方法入参
$ watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b
条件限制    - #只有满足条件的调用,才会有响应
$ watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
观察异常信息的例子
$ watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2
按照耗时进行过滤      - #cost>200(单位是ms)
#watch/stack/trace这个三个命令都支持#cost
$ watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2
观察当前对象中的属性
$ watch demo.MathGame primeFactors 'target'
使用target.field_name访问当前对象的某个属性
$ watch demo.MathGame primeFactors 'target.illegalArgumentCount'
观察类中所有抛出异常的方法信息
$ watch com.example.UserService * -e -x 2 '{params,throwExp}'

同时观察方法调用前和方法返回后
#参数里-n 2,表示只执行两次
#结果的输出顺序和事件发生的先后顺序一致,和命令中 -s -b 的顺序无关
#-x表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1
$ watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2

特别说明

观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。
用法:https://alibaba.github.io/arthas/advice-class.html


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

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

jvm相关

mc 编译指令

Memory Compiler/内存编绎器,编绎.java文件生成.class。
mc /tmp/Test.java
可以通过-c参数指定classloader:
mc -c 327a647b /tmp/Test.java
可以通过-d命令指定输出目录:
mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java

编绎生成.class文件之后,可以结合redefine命令实现热更新代码

动态增加日志 redefine

在Arthas里,可以通过redefine命令来达到线上不重启,动态更新代码的效果
添加日志,本地编绎后,把.class文件传到线上服务器,然后用redefine命令来更新代码

$ redefine -p /tmp/UserServiceImpl.class
redefine success, size: 1

记录调用

$ tt -t com.example.webtest.controller.testController testResult
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 12 ms.
 INDEX     TIMESTAMP              COST(ms)    IS-RET    IS-EXP   OBJECT            CLASS                               METHOD
----------------------------------------------------------------------------------------------------------------------------------------------------------
 1002      2019-02-12 16:14:25    0.367999    true      false    0x6ad213f7        testController                      testResult

命令参数解析
-t
tt 命令有很多个主参数,-t 就是其中之一。这个参数的表明希望记录下类 *Test 的 print 方法的每次执行情况。

-n 3
当你执行一个调用量不高的方法时可能你还能有足够的时间用 CTRL+C 中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。
此时你可以通过 -n 参数指定你需要记录的次数,当达到记录次数时 Arthas 会主动中断tt命令的记录过程,避免人工操作无法停止的情况。

解决方法重载
tt -t *Test print params[0].length==1
通过制定参数个数的形式解决不同的方法签名,如果参数个数一样,你还可以这样写
tt -t *Test print 'params[1] instanceof Integer'
解决指定参数
tt -t *Test print params[0].mobile=="13989838402"
查看检索到的记录
tt -l
查看调用具体信息
tt -i 1003
对于具体一个时间片的信息而言,你可以通过 -i 参数后边跟着对应的 INDEX 编号查看到他的详细信息
回放调用
tt -i 1004 -p

$ tt -i 1004 -p
 RE-INDEX       1004
 GMT-REPLAY     2018-12-04 11:26:00
 OBJECT         0x4b67cf4d
 CLASS          demo.MathGame
 METHOD         primeFactors
 PARAMETERS[0]  @Integer[946738738]
 IS-RETURN      true
 IS-EXCEPTION   false
 RETURN-OBJ     @ArrayList[
                    @Integer[2],
                    @Integer[11],
                    @Integer[17],
                    @Integer[2531387],
                ]
Time fragment[1004] successfully replayed.
Affect(row-cnt:1) cost in 14 ms.

tt 命令由于保存了当时调用的所有现场信息,所以我们可以自己主动对一个 INDEX 编号的时间片自主发起一次调用,从而解放你的沟通成本。此时你需要 -p 参数。

需要强调的点


获取Spring context

除了上面介绍的一些排查技巧,下面分享一个获取Spring Context,然后“为所欲为”的例子。
在Dubbo里有一个扩展com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory,把Spring Context保存到了里面。
因此,我们可以通过ognl命令获取到。

$ ognl '#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next, #context.getBean("userServiceImpl").findUser(1)'
@User[
    id=@Integer[1],
    name=@String[Deanna Borer],
]
SpringExtensionFactory@contexts.iterator.next 获取到SpringExtensionFactory里保存的spring context对象
#context.getBean("userServiceImpl").findUser(1) 获取到userServiceImpl再执行一次调用

Dubbo运行时有哪些Filter? 耗时是多少?

trace函数
-j 过滤掉jdk的函数

$ trace -j  demo.MathGame run
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 31 ms.
`---ts=2018-12-04 01:09:14;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
    `---[5.190646ms] demo.MathGame:run()
        +---[4.465779ms] demo.MathGame:primeFactors()
        `---[0.375324ms] demo.MathGame:print()

据调用耗时过滤
trace demo.MathGame run '#cost > 10'

$ trace com.alibaba.dubbo.rpc.Filter *
Press Ctrl+C to abort.
Affect(class-cnt:19 , method-cnt:59) cost in 1441 ms.
`---ts=2018-12-05 19:07:26;thread_name=DubboServerHandler-30.5.125.152:20880-thread-10;id=3e;is_daemon=true;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@5c647e05
    `---[8.435844ms] com.alibaba.dubbo.rpc.filter.EchoFilter:invoke()
        +---[0.124572ms] com.alibaba.dubbo.rpc.Invocation:getMethodName()
        +---[0.065123ms] java.lang.String:equals()
        `---[7.762928ms] com.alibaba.dubbo.rpc.Invoker:invoke()
            `---[7.494124ms] com.alibaba.dubbo.rpc.filter.ClassLoaderFilter:invoke()
                +---[min=0.00355ms,max=0.049922ms,total=0.057637ms,count=3] java.lang.Thread:currentThread()
                +---[0.0126ms] java.lang.Thread:getContextClassLoader()
                +---[0.02188ms] com.alibaba.dubbo.rpc.Invoker:getInterface()
                +---[0.004115ms] java.lang.Class:getClassLoader()
                +---[min=0.003906ms,max=0.014058ms,total=0.017964ms,count=2] java.lang.Thread:setContextClassLoader()
                `---[7.033486ms] com.alibaba.dubbo.rpc.Invoker:invoke()
                    `---[6.869488ms] com.alibaba.dubbo.rpc.filter.GenericFilter:invoke()
                        +---[0.01481ms] com.alibaba.dubbo.rpc.Invocation:getMethodName()


动态修改Dubbo的logger级别

在排查问题时,需要查看到更多的信息,如果可以把logger级别修改为DEBUG,就非常有帮助。
ognl是apache开源的一个轻量级表达式引擎。下面通过Arthas里的ognl命令来动态修改logger级别。
首先获取Dubbo里TraceFilter的一个logger对象,看下它的实现类,可以发现是log4j

$ ognl '@com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter@logger.logger'
@Log4jLogger[
    FQCN=@String[com.alibaba.dubbo.common.logger.support.FailsafeLogger],
    logger=@Logger[org.apache.log4j.Logger@2f19bdcf],
]

再用sc命令来查看具体从哪个jar包里加载的

$ sc -d org.apache.log4j.Logger
 class-info        org.apache.log4j.Logger
 code-source       /Users/hengyunabc/.m2/repository/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar
 name              org.apache.log4j.Logger
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       Logger
 modifier          public
 annotation
 interfaces
 super-class       +-org.apache.log4j.Category
                     +-java.lang.Object
 class-loader      +-sun.misc.Launcher$AppClassLoader@5c647e05
                     +-sun.misc.Launcher$ExtClassLoader@59878d35
 classLoaderHash   5c647e05

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

可以看到log4j是通过slf4j代理的。
那么通过org.slf4j.LoggerFactory获取root logger,再修改它的level:

$ ognl '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
null
$ ognl '@org.slf4j.LoggerFactory@getLogger("root").getLevel().toString()'
@String[DEBUG]

sc命令

“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息

打印类的详细信息
sc -d com.example.webtest.controller.testController
打印出类的Field信息
sc -d -f demo.MathGame

ognl

https://commons.apache.org/proper/commons-ognl/language-guide.html

$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
@ArrayList[
    @String[/opt/java/8.0.181-zulu/jre],
    @String[OpenJDK Runtime Environment],
]
上一篇下一篇

猜你喜欢

热点阅读