Arthas 类相关命令 jad/sc/ognl/watch/trace
前面的文章,我们讲解了Arthas 可以用来监控系统信息,比如dashboard监控内存,cpu,jvm打印系统变量等信息
Arthas之所以称之为神器,不只有上面的那些小case,真正厉害的是实战中的这些类相关功能,能让你体验到掌控一切的感觉,爽到爆,本文主要讲解Arthas 操作类相关功能 jad/sc/sm/mc/redefine
1.jad 反编译命令
jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码
- 反编译出来的源码是带语法高亮的,阅读更方便
- 对比类加载的详细信息比较清楚,到底这段代码是不是最新的,是不是修改过的,一眼看穿
- 当我们看到某个方法的调用时长明显过长,可以直接反编译该方法,定位原因
执行 jad com.jzj.jvmtest.font.TestController
2. sc/sm查看类及类方法信息
sc支持通配符搜索类信息
- sc *Controller 查看以Controller结尾的 类信息
- sc -d 查看类的详细信息
- sc -d *TestController 通配符查找TestController的类信息
sm用于搜索类的方法信息
- sm *TestController 查找TestController 的所有方法信息
- sm -d 查找类具体方法信息
- sm -d *TestController 查找类TestController的具体方法信息
执行 sc *Controller
查找所有以Controller结尾的类信息
[arthas@4404]$ sc *Controller
com.jzj.jvmtest.font.TestController
com.sun.proxy.$Proxy37
com.sun.proxy.$Proxy63
com.taobao.arthas.core.shell.system.JobController
com.taobao.arthas.core.shell.system.impl.GlobalJobControllerImpl
com.taobao.arthas.core.shell.system.impl.JobControllerImpl
java.security.AccessController
org.apache.naming.ContextAccessController
org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController
org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
org.springframework.boot.web.servlet.error.ErrorController
org.springframework.stereotype.Controller
org.springframework.web.bind.annotation.RestController
sun.net.www.protocol.jar.JarFileFactory
sun.net.www.protocol.jar.URLJarFile$URLJarFileCloseController
Affect(row-cnt:15) cost in 11 ms.
执行 sc -d *TestController 查找 所有TestController的类信息
- isInterface是否是接口
- isAnnotation 是否有注解
- isEnum是否是枚举等
- isAnonymousClass 是否是匿名类
- isArray是否是数组
- modifier public 类型是public
[arthas@4404]$ sc -d *TestController
class-info com.jzj.jvmtest.font.TestController
code-source /E:/myworkspace/distribute/jvmtest/target/classes/
name com.jzj.jvmtest.font.TestController
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name TestController
modifier public
annotation org.springframework.web.bind.annotation.RestController
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@512ddf17
执行 sm *TestController 查看TestController的类方法信息
[arthas@4404]$ sm *TestController
com.jzj.jvmtest.font.TestController <init>()V
com.jzj.jvmtest.font.TestController test(Ljava/lang/Integer;)Ljava/lang/String;
com.jzj.jvmtest.font.TestController cpu()Ljava/lang/String;
com.jzj.jvmtest.font.TestController writelog(Ljava/lang/Integer;)Ljava/lang/String;
com.jzj.jvmtest.font.TestController ping()Ljava/lang/String;
Affect(row-cnt:5) cost in 8 ms.
sm -d *TestController 查看TestController方法的具体信息,入参,出参,返回值,异常信息等等
[arthas@4404]$ sm -d *TestController
declaring-class com.jzj.jvmtest.font.TestController
method-name test
modifier private
annotation org.springframework.web.bind.annotation.GetMapping
parameters java.lang.Integer
return java.lang.String
exceptions
classLoaderHash 18b4aac2
declaring-class com.jzj.jvmtest.font.TestController
method-name cpu
modifier private
annotation org.springframework.web.bind.annotation.GetMapping
parameters
return java.lang.String
exceptions
classLoaderHash 18b4aac2
3.mc Memory Compiler内存编译器
mc命令可以执行内存编译器,把文件编译成 .class文件,可以使用如下命令
- mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/xxx.java -d /tmp 使用classLoaderClass 编译xxx.java文件
- mc -c xxxxx123 /tmp/xxx.java -d /tmp 使用java进程的类加载器xxxxx123是通过 sc -d 查看的id来编译 xxx.java文件
注意 反编译的时候不要改 类的名字 ,不要改类的名字 ,不要改类的名字,会报错 "类 TestController 是公共的, 应在名为 TestController.java 的文件中声明"
比如你把TestController.java 改成了AA.java ,然后再次mc执行内存编译的时候会报错, 因为Java文件的里面还是TestController,只是文件名字被你换成了AA,所以编译是不通过的
2023-05-07 00:10:06 [arthas-command-execute] WARN c.t.a.c.c.k.MemoryCompilerCommand -Memory compiler error
com.taobao.arthas.compiler.DynamicCompilerException: Compilation Error
message: 类 TestController 是公共的, 应在名为 TestController.java 的文件中声明 , line: 18 ,
at com.taobao.arthas.compiler.DynamicCompiler.buildByteCodes(DynamicCompiler.java:132)
at com.taobao.arthas.core.command.klass100.MemoryCompilerCommand.process(MemoryCompilerCommand.java:136)
下面我们来实战一下 mc 内存编译器的使用
3.1 jad反编译TestController
- 我们把TestController反编译一下, 保存成 tmp/TestController.java文件 , 可以看到 /tmp文件夹下有了 TestController.java文件
jad --source-only com.jzj.jvmtest.font.TestController > /tmp/TestController.java
3.2 sc -d查找类加载信息,修改TestController
- 修改TestController.java文件 我们修改下 日志打印信息
/ log.debug("debug ===========111");
/* 37*/ log.info("info ===========222");
/* 38*/ log.error("error ===========333");
- 查找TestController的 classLoader类加载器信息
[arthas@25584]$ sc -d *TestController |grep classLoader
classLoaderHash 619a5dff
3.3 mc内存编译新的TestController文件
- mc内存编译 TestController.java
- 执行mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/TestController.java -d /tmp
[arthas@22740]$ mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/TestController.java -d /tmp
Memory compiler output:
E:\tmp\com\jzj\jvmtest\font\TestController.class
Affect(row-cnt:1) cost in 542 ms.
mc编译成功
除了使用 --classLoaderClass 指定编译器外, 我们可以使用 -c参数简化命令 执行 mc -c 619a5dff(前面的classLoader类加载器信息id) /tmp/TestController.java -d /tmp
mc -c 619a5dff /tmp/TestController.java -d /tmp
重新编译成功, 可以看到时间就是我们刚执行的 时间
4.redefine 重定义/热加载
使用redefine命令重新加载新编译好的class文件加载到 JVM进程中,从而达到不重启应用就能够实现热加载,修改类的class信息
4.1 原始文件测试
首先看下我们的TestController
package com.jzj.jvmtest.font;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class TestController {
@GetMapping("/ping")
private String ping() {
log.info("sssss");
return "pong";
}
@GetMapping("/print")
private String getScore(Integer score) {
log.debug("debug ===========");
log.info("info ===========");
log.error("error ===========");
//输入分数超过 100, 就取100,否则返回真正的分数
if (score > 100) {
return "10";
}
return String.valueOf(score);
}
}
执行 curl 127.0.0.1:8078/print?score=60 应该返回 60,并且日志打印 info =========== 和 error ===========, 看下结果
#返回 60分
C:\Users\jzj>curl 127.0.0.1:8078/print?score=60
60
#打印日志
2023-05-07 00:34:38.982 [http-nio-8078-exec-2] INFO com.jzj.jvmtest.font.TestController - [getScore,20] - info ===========
2023-05-07 00:34:38.982 [http-nio-8078-exec-2] ERROR com.jzj.jvmtest.font.TestController - [getScore,21] - error ===========
执行信息及日志如下
4.2 redefine热更新TestController
下面 我们把刚才 mc内存编译过的 TestController文件
加载到内存 redefine /tmp/com/jzj/jvmtest/font/TestController.class
[arthas@22740]$ redefine /tmp/com/jzj/jvmtest/font/TestController.class
redefine success, size: 1, classes:
com.jzj.jvmtest.font.TestController
[arthas@22740]$
再次执行 curl 127.0.0.1:8078/print?score=60
#返回 60分
C:\Users\jzj>curl 127.0.0.1:8078/print?score=60
60
#打印新的类的日志 222/333
2023-05-07 00:42:20.268 [http-nio-8078-exec-4] INFO com.jzj.jvmtest.font.TestController - [getScore,24] - info ===========222
2023-05-07 00:42:20.268 [http-nio-8078-exec-4] ERROR com.jzj.jvmtest.font.TestController - [getScore,25] - error ===========333
日志信息已经被修改,重新加载了新的日志信息 222/333,是我们刚才修改的TestControler
使用redefine 从而实现了热更新
至此,我们已经学习了 Arthas的基本命令,包括jad反编译,sc查看类命令,sm查看来类方法命令,mc内存编译及redefine热更新,可以更好的利用Arthas排查线上问题