美文网首页
arthas学习

arthas学习

作者: icecrea | 来源:发表于2019-02-15 11:44 被阅读1次

    启动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
    

    特别说明

    • watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后
    • 4个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
    • 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参
    • 当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在

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


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

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

    jvm相关

    • dashboard——当前系统的实时数据面板
    • thread——查看当前 JVM 的线程堆栈信息
    • jvm——查看当前 JVM 的信息
    • sysprop——查看和修改JVM的系统属性
    • sysenv——查看JVM的环境变量
    • getstatic——查看类的静态属性
    • New! ognl——执行ognl表达式

    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 参数。

    需要强调的点

    • ThreadLocal 信息丢失
      很多框架偷偷的将一些环境变量信息塞到了发起调用线程的 ThreadLocal 中,由于调用线程发生了变化,这些 ThreadLocal 线程信息无法通过 Arthas 保存,所以这些信息将会丢失。
      一些常见的 CASE 比如:鹰眼的 TraceId 等。
    • 引用的对象
      需要强调的是,tt 命令是将当前环境的对象引用保存起来,但仅仅也只能保存一个引用而已。如果方法内部对入参进行了变更,或者返回的对象经过了后续的处理,那么在 tt 查看的时候将无法看到当时最准确的值。这也是为什么 watch 命令存在的意义。

    获取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],
    ]
    

    相关文章

      网友评论

          本文标题:arthas学习

          本文链接:https://www.haomeiwen.com/subject/jeckeqtx.html