JVM系列(四十一) JVM调优实战-Arthas 类相关命令 jad/sc/sm/mc/redefine

651 阅读3分钟

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

image.png

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

image.png

3.2 sc -d查找类加载信息,修改TestController
  • 修改TestController.java文件 我们修改下 日志打印信息
/         log.debug("debug ===========111");
/* 37*/         log.info("info ===========222");
/* 38*/         log.error("error ===========333");

image.png

  • 查找TestController的 classLoader类加载器信息
[arthas@25584]$ sc -d *TestController |grep classLoader
 classLoaderHash   619a5dff

image.png

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编译成功 image.png

除了使用 --classLoaderClass 指定编译器外, 我们可以使用 -c参数简化命令 执行 mc -c 619a5dff(前面的classLoader类加载器信息id) /tmp/TestController.java -d /tmp

mc -c  619a5dff  /tmp/TestController.java -d /tmp

重新编译成功, 可以看到时间就是我们刚执行的 时间 image.png

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 ===========

执行信息及日志如下 image.png

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]$

image.png

再次执行 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 image.png

使用redefine 从而实现了热更新


至此,我们已经学习了 Arthas的基本命令,包括jad反编译,sc查看类命令,sm查看来类方法命令,mc内存编译及redefine热更新,可以更好的利用Arthas排查线上问题