Arthas(阿尔萨斯)使用

阿里巴巴开源的Java诊断工具
https://arthas.aliyun.com/doc/commands.html

安装

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

启动后,会展示机器上的java进程,输入序号,即可attach到对应的进程

卸载

rm -rf ~/.arthas/
rm -rf ~/logs/arthas/

使用

dashboard 概要展示目标jvm的线程、内存、gc、vm,tomcat信息

展示当前进程的简要报告
类似于

$ dashboard
ID     NAME                   GROUP          PRIORI STATE  %CPU    TIME   INTERRU DAEMON
17     pool-2-thread-1        system         5      WAITIN 67      0:0    false   false
27     Timer-for-arthas-dashb system         10     RUNNAB 32      0:0    false   true
11     AsyncAppender-Worker-a system         9      WAITIN 0       0:0    false   true
9      Attach Listener        system         9      RUNNAB 0       0:0    false   true
3      Finalizer              system         8      WAITIN 0       0:0    false   true
2      Reference Handler      system         10     WAITIN 0       0:0    false   true
4      Signal Dispatcher      system         9      RUNNAB 0       0:0    false   true
26     as-command-execute-dae system         10     TIMED_ 0       0:0    false   true
13     job-timeout            system         9      TIMED_ 0       0:0    false   true
1      main                   main           5      TIMED_ 0       0:0    false   false
14     nioEventLoopGroup-2-1  system         10     RUNNAB 0       0:0    false   false
18     nioEventLoopGroup-2-2  system         10     RUNNAB 0       0:0    false   false
23     nioEventLoopGroup-2-3  system         10     RUNNAB 0       0:0    false   false
15     nioEventLoopGroup-3-1  system         10     RUNNAB 0       0:0    false   false
Memory             used   total max    usage GC
heap               32M    155M  1820M  1.77% gc.ps_scavenge.count  4
ps_eden_space      14M    65M   672M   2.21% gc.ps_scavenge.time(m 166
ps_survivor_space  4M     5M    5M           s)
ps_old_gen         12M    85M   1365M  0.91% gc.ps_marksweep.count 0
nonheap            20M    23M   -1           gc.ps_marksweep.time( 0
code_cache         3M     5M    240M   1.32% ms )
Runtime
os.name                Mac OS X
os.version             10.13.4
java.version           1.8.0_162
java.home              /Library/Java/JavaVir
                       tualMachines/jdk1.8.0
                       _162.jdk/Contents/Hom
                       e/jre

thread 显示线程信息,线程堆栈

展示ID为1的线程栈(通常为主线程)

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

展示当前最忙的前N个线程并打印堆栈

thread -n 3

没有参数时,显示第一页线程的信息

默认按CPU增量时间降序排列

显示所有的线程

thread --all

找出当前阻塞其他线程的线程

thread -b

指定采样时间间隔

thread -i

  • thread -i 1000: 统计最近1000ms内的线程CPU时间
  • thread -n 3 -i 1000: 列出1000ms内最忙的3个线程栈

查看指定状态的线程

thread --state
可选值:NEW, RUNNABLE, TIMED_WAITING, WAITING, BLOCKED, TERMINATED

jad 反编译

jad demo.MathGame

watch 显示特定方法调用的输入/输出参数,返回值,抛出的异常

表达式类型

           target : the object
            clazz : the object's class
           method : the constructor or method
           params : the parameters array of method
     params[0..n] : the element of parameters array
        returnObj : the returned object of method
         throwExp : the throw exception of method
         isReturn : the method ended by return
          isThrow : the method ended by throwing exception
            #cost : the execution time in ms of method invocation

eg. 显示demo.MathGame#primeFactors的返回值

watch demo.MathGame primeFactors returnObj

监听程序入参和返回值

原始程序

public class UtilController {
    public RespVo> getUrl(@Validated @RequestBody GetUrlReqVo req) {
        Date expire = Date.from(LocalDateTime.now().plusMinutes(10L).atZone(ZoneId.systemDefault()).toInstant());
        final List list = req.getPaths().stream().filter(StringUtils::isNotBlank).map(t -> {
            final URL url = ossTemplate.generatePresignedUrl(t, expire);
            return new PathUrlVo(t, url.toString());
        }).collect(Collectors.toList());
        return RespUtil.success(list);
    }
}

监听参数和返回值

[arthas@20431]$ watch controller.UtilController getUrl '{params, returnObj}' -x 3
Press Q or Ctrl+C to abort.
Affect(class count: 2 , method count: 2) cost in 102 ms, listenerId: 17
method=controller.UtilController.getUrl location=AtExit
ts=2021-03-31 09:46:50; [cost=14.647368ms] result=@ArrayList[
    @Object[][
        @GetUrlReqVo[
            paths=@ArrayList[isEmpty=false;size=1],
        ],
    ],
    @RespVo[
        code=@Integer[0],
        message=@String[操作成功],
        data=@ArrayList[
            @PathUrlVo[PathUrlVo(path=apply/20210308/e0f93d12fa6f40298844f5203a0b7cb0.png, url=http://xxx.oss-cn-hangzhou.aliyuncs.com/apply/20210308/e0f93d12fa6f40298844f5203a0b7cb0.png?Expires=1617155810&OSSAccessKeyId=xxx&Signature=ruZfoV55FP%2F2Svi06dbLXyU91kY%3D)],
        ],
        debug=null,
    ],
]

sc 搜索JVM加载的所有类

Search-Class

$ sc -d *MathGame

 class-info        demo.MathGame                                                                    
 code-source       /root/math-game.jar                                                              
 name              demo.MathGame                                                                    
 isInterface       false                                                                            
 isAnnotation      false                                                                            
 isEnum            false                                                                            
 isAnonymousClass  false                                                                            
 isArray           false                                                                            
 isLocalClass      false                                                                            
 isMemberClass     false                                                                            
 isPrimitive       false                                                                            
 isSynthetic       false                                                                            
 simple-name       MathGame                                                                         
 modifier          public                                                                           
 annotation                                                                                         
 interfaces                                                                                         
 super-class       +-java.lang.Object                                                               
 class-loader      +-jdk.internal.loader.ClassLoaders$AppClassLoader@c387f44                        
                     +-jdk.internal.loader.ClassLoaders$PlatformClassLoader@560eeb6                 
 classLoaderHash   c387f44 

sm 查看已加载类的方法信息

Search-Method
只能看到由当前类所声明的方法,父类则无法看到

trace 方法调用链路,并输出方法路径上每个节点的耗时

主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路
-n, --limits x 执行次数限制
--skipJDKMethod true/false 是否跳过jdk方法追踪(默认false)

$ trace server.controller.UtilController getUrl -n 1
Press Q or Ctrl+C to abort.
Affect(class count: 2 , method count: 2) cost in 118 ms, listenerId: 2
`---ts=2021-03-31 15:38:56;thread_name=http-nio-8080-exec-4;id=48;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@279c4e3b
    `---[116.384616ms] server.controller.UtilController$$EnhancerBySpringCGLIB$$d248af01:getUrl() [throws Exception]
        +---[116.24846ms] org.springframework.cglib.proxy.MethodInterceptor:intercept() #95 [throws Exception]
        |   `---[26.746788ms] server.controller.UtilController:getUrl() [throws Exception]
        |       +---[0.028009ms] server.router.BizRouterContext:get() #105
        |       +---[26.275524ms] server.router.global.AuthTokenCheckBizRouter:process() #106 [throws Exception]
        |       `---throw:common.exception.SystemException #145 [code:401, msg:无效的Token]
        `---throw:common.exception.SystemException #145 [code:401, msg:无效的Token]

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

很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令

mc 内存编译器

retransform 加载外部class文件

mc: 在内存里,将java文件编译成字节码和class文件
retransform 加载外部class文件,retransform JVM中已加载的类

retransform指定的class文件

$ retransform /tmp/MathGame.class
retransform success, size: 1, classes:
demo.MathGame

加载指定的class文件,解析出class name,再retransform jvm中已加载的对应的类。每加载一个class文件,都会记录一个retransform entry

如果多次执行retransform加载同一个class文件,则会有多条retransform entry

查看retransform entry

$ retransform -l
Id              ClassName       TransformCount  LoaderHash      LoaderClassName
1

删除指定retransform

#删除指定retransform entry
retransform -d 1
#删除所有
retransform --deleteAll

显示触发retransform

$ retransform --classPattern demo.MathGame
retransform success, size: 1, classes:
demo.MathGame

对于同一个类,当存在多个retransform entry时,如果显示触发,则最后添加的entry生效(id最大的)

消除retransform影响

如果对某个类执行retransform之后,想要消除影响,则需要:

  • 删除这个类对应的retransform entry
  • 重新触发retransform

如果不清除掉所有的retransform entry,并重新触发retransform,则arthas stop时,retransform过的类仍然生效
结合jad/mc命令使用

#jad命令反编译,之后可以使用其他编辑器,如vim来修改源码
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
 #mc命令来内存编译修改过的代码
mc /tmp/UserController.java -d /tmp
 #加载新的字节码
retransform /tmp/com/example/demo/arthas/user/UserController.class

上传class文件到服务器的技巧

使用mc命令来编译jad的反编译的代码有可能失败。可以在本地修改代码,编译好后再上传到服务器上。有的服务器不允许直接上传文件,可以使用base64命令来绕过

# 1. 在本地先转换class文件为base64,再保存为result.txt
base64 Test.class result.txt
# 2.到服务器上,新建并编辑result.txt,复制本地的内容,粘贴并保存
# 3.把服务器上的result.txt还原为.class
base64 -d result.txt Test.class
# 4.用md5命令计算哈希值,校验是否一致

retransform限制

  • 不允许新增field/method
  • 正在运行的函数,没有退出不能生效
public class MathGame {
    public static void main(String[] args) throws InterruptedException {
        MathGame game = new MathGame();
        while (true) {
            game.run();
            TimeUnit.SECONDS.sleep(1);
            // 这个不生效,因为代码一直跑在 while里
            System.out.println("in loop");
        }
    }
 
    public void run() throws InterruptedException {
        // 这个生效,因为run()函数每次都可以完整结束
        System.out.println("call run()");
        try {
            int number = random.nextInt();
            List primeFactors = primeFactors(number);
            print(number, primeFactors);
 
        } catch (Exception e) {
            System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
        }
    }

sc retransform实例

# 1.启动math-game
wget https://github.com/hengyunabc/spring-boot-inside/raw/master/demo-arthas-spring-boot/demo-arthas-spring-boot.jar
java -jar demo-arthas-spring-boot.jar
# 2.启动arthas-boot
wget https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 3.访问接口,会有错误信息
curl http://localhost/user/0
# 4.使用jad反编译UserController
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
# 5.编辑UserController
vim /tmp/UserController.java
:<

tt 方法执行数据的时空隧道

记录指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
TimeTunnel,记录当时方法调用的所有入参和返回值、抛出的异常

  • ThreadLocal信息丢失,无法通过Arthas保存
  • 引用的对象
    tt是将当前环境的对象引用保存起来,如果方法内部对入参进行了变更,或返回的对象经过了后续处理,在tt查看的时候将无法看到当时准确的值
    记录当前方法的调用现场:
    参数:-t 记录方法调用;-n 3,指定记录的次数
tt -t demo.MathGame primeFactors
#解决方法重载
tt -t *Test print params.length==1
tt -t *Test print 'params[1] instanceof Integer'
#解决指定参数
tt -t *Test print params[0].mobile=="13989838402"

查看调用记录

$ tt -l
INDEX   TIMESTAMP            COST(ms)  IS-RET  IS-EXP   OBJECT         CLASS                          METHOD
-------------------------------------------------------------------------------------------------------------------------------------
 1000    2018-12-04 11:15:38  1.096236  false   true     0x4b67cf4d     MathGame                       primeFactors
 1001    2018-12-04 11:15:39  0.191848  false   true     0x4b67cf4d     MathGame                       primeFactors
 1002    2018-12-04 11:15:40  0.069523  false   true     0x4b67cf4d     MathGame                       primeFactors

查看调用信息

tt -i 1002

重做一次调用

tt -i 1002 -p

logger

查看logger信息,更新logger level

[arthas@22033]$ logger
 name                              root
 class                             org.apache.logging.log4j.core.async.AsyncLoggerConfig
 classLoader                       sun.misc.Launcher$AppClassLoader@18b4aac2
 classLoaderHash                   18b4aac2
 level                             INFO
 config                            XmlConfiguration[location=/Users/qudian/IdeaProjects/external/gateway/gw-server/target/classes/log4j2-spring-local.xml]
 additivity                        true
 codeSource                        file:/Users/qudian/.m2/repository/org/apache/logging/log4j/log4j-core/2.13.3/log4j-core-2.13.3.jar
 appenders                         name            console
                                   class           org.apache.logging.log4j.core.appender.ConsoleAppender
                                   classLoader     sun.misc.Launcher$AppClassLoader@18b4aac2
                                   classLoaderHash 18b4aac2
                                   target          SYSTEM_OUT

 name                              lci.gw.dao
 class                             org.apache.logging.log4j.core.config.LoggerConfig
 classLoader                       sun.misc.Launcher$AppClassLoader@18b4aac2
 classLoaderHash                   18b4aac2
 level                             DEBUG
 config                            XmlConfiguration[location=/Users/qudian/IdeaProjects/external/gateway/gw-server/target/classes/log4j2-spring-local.xml]
 additivity                        false
 codeSource                        file:/Users/qudian/.m2/repository/org/apache/logging/log4j/log4j-core/2.13.3/log4j-core-2.13.3.jar
 appenders                         name            console
                                   class           org.apache.logging.log4j.core.appender.ConsoleAppender
                                   classLoader     sun.misc.Launcher$AppClassLoader@18b4aac2
                                   classLoaderHash 18b4aac2
                                   target          SYSTEM_OUT

更新logger level

[arthas@22033]$ logger -n lci.gw.dao --level debug
Update logger level success.

vmtool

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

# 获取对象
vmtool --action getInstances --className java.lang.String --limit 10

# 指定 classloader hash
#可以通过sc命令查找到加载 class 的 classloader
sc -d org.springframework.context.ApplicationContext
# 然后用-c/--classloader 参数指定

# 指定返回结果展开的层数
vmtool --action getInstances -c 19469ea2 --className org.springframework.context.ApplicationContext -x 2

# 执行表达式
# 调用UserController#findUserById
vmtool --action getInstances --className com.example.demo.arthas.user.UserController --express 'instances[0].findUserById(1)'

# 强制GC
vmtool --action forceGc

# interrupt 指定线程
vmtool --action interruptThread -t 1

mbean 查看MBean的信息

ognl 执行ognl表达式

调用静态方法

ognl '@[email protected]("hello")'
获取静态类的静态字段
ognl '@demo.MathGame@random'

dump dump已加载类的bytecode到特定目录

heapdump 类似jmap命令的heap dump

classloader 查看classloader的继承树,urls,类加载信息

monitor 方法执行监控

非实时返回命令,服务端是以人物的形式在后台跑任务

profiler 使用async-profiler生成火焰图

重新连接

telnet 127.0.0.1 3658

退出

quit 或者 exit

与Skywalking-agent兼容问题

在启动命令上,添加参数:

-Dskywalking.agent.is_cache_enhanced_class=true -Dskywalking.agent.class_cache_mode=MEMORY

https://github.com/apache/skywalking/blob/master/docs/en/FAQ/Compatible-with-other-javaagent-bytecode-processing.md

原因说明

你可能感兴趣的:(Arthas(阿尔萨斯)使用)