JAVA进阶随笔

【性能优化】Arthas 原理浅谈

2022-06-15  本文已影响0人  熊本极客

Arthas 是基于 ASMJava Agent 技术实现的 Java 诊断利器。
ASM 是指一个 Java 字节码操作框架,用于动态生成或者增强 class。
采用 Attach API 方式的 Java Agent 是指在 JVM 启动后通过 Attach API 执行 agentmian 方法,利用 Instrumentation API 的 debug 和 profiler 能力。

1 Arthas 原理概览

Arthas 除了利用 JDK 自带的工具,例如查看堆内存 jmap、查看线程 jstack 等。Arthas 整体模块的调用图如下:


image.png

Arthas 还可以解决如下问题
① 如果没有日志,如何定位问题或者统计数据?
② 有什么办法可以监控到 JVM 的实时运行状态?
③ 怎么快速定位应用的热点,生成火焰图?

问题:Arthas 如何实现自定义的无侵入式数据采集?
解答:Arthas 利用 Java Agent 通过 Attach API 运行时加载目标 Java 程序,最终利用 Instrumentation API 或者 ASM 增强 class。

image.png

2 核心机制

2.1 Java Agent

2.1.1 Java Agent 的加载方式

Java Agent 有 2 种加载方式,1.利用启动参数 -javaagent 启动时加载方式;2.Attach API 运行时的加载方式。

#加载方式1:JVM 启动时候加载,通过 javaagent 启动参数 java -javaagent:myagent.jar MyMain。该种方式需要程序 main 方法执行之前执行 agent 中的 premain 方法
 public static void premain(String agentArgs)
 public static void premain(String agentArgument, Instrumentation instrumentation) throws Exception
#加载方式2:JVM 运行时 Attach API 加载。该方式会在 agent 加载以后执行 agentmain 方法
 public static void agentmain(String agentArgs)
 public static void agentmain(String agentArgument, Instrumentation instrumentation) throws Exception

方式1:利用启动参数 -javaagent 启动时的加载方式
premain 方法是在启动时,类加载前定义类的 TransFormer,在类加载的时候更新对应的类的字节码。

premain 方法的执行步骤如下:
① 创建 InstrumentationImpl 对象
② 监听 ClassFileLoadHook 事件
③ 调用 InstrumentationImplloadClassAndCallPremain 方法,最终调用 javaagent 里 MANIFEST.MF 里指定的 Premain-Class 类的 premain 方法

image.png

方式2:Attach API 运行时的加载方式
Attach API 在运行时进行类的字节码的修改,关键是注册类的 TransFormer、调用 retransformClasses 函数重加载类。

agentmain 方法的执行步骤如下:
① 创建 InstrumentationImpl 对象
② 监听 ClassFileLoadHook 事件
③ 调用 InstrumentationImplloadClassAndCallAgentmain 方法,最终调用 javaagent 里 MANIFEST.MF 里指定的 Agentmain-Class 类的 agentmain 方法

image.png

2.1.2 Java Instrumentation 介绍

Instrumentation 是一个 JVM 接口,该接口提供了一组查看和操作 Java 类的方法,例如修改类的字节码、向 classLoader 的 classpath 下加入 jar 文件等。开发者可以通过 Java 语言来操作和监控 JVM 内部的状态,进而实现 Java 程序的监控分析。Instrumentation 整体的执行流程可以参考文章 Java Instrumentation 原理

Instrumentation 关键的源码如下

public interface Instrumentation {
    /**
     * 注册一个Transformer,从此之后的类加载都会被Transformer拦截。
     * Transformer可以直接对类的字节码byte[]进行修改
     */
    void addTransformer(ClassFileTransformer transformer);
    
    /**
     * 对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。
     * retransformation可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性
     */
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
    
    /**
     * 获取一个对象的大小
     */
    long getObjectSize(Object objectToSize);
    
    /**
     * 将一个jar加入到bootstrap classloader的 classpath里
     */
    void appendToBootstrapClassLoaderSearch(JarFile jarfile);
    
    /**
     * 获取当前被JVM加载的所有类对象
     */
    Class[] getAllLoadedClasses();
}

上述最常用的方法就是 addTransformer(ClassFileTransformer transformer)。该方法可以在类加载时做拦截,对输入的类的字节码进行修改,其参数是一个 ClassFileTransformer 接口,定义如下:

/**
 * 传入参数表示一个即将被加载的类,包括了classloader,classname和字节码byte[]
 * 返回值为需要被修改后的字节码byte[]
 */
byte[]
transform(  ClassLoader         loader,
            String              className,
            Class<?>            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer)  throws IllegalClassFormatException;

Instrumentation 实现热修改类的样例
步骤 1:instrumentationAgent.jar 实现 premain 方法。

public class InstrumentationExample {
    public static void premain(String args, Instrumentation inst) {
        // Instrumentation提供的addTransformer方法,在类加载时会回调ClassFileTransformer接口
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain, byte[] classfileBuffer)
                                    throws IllegalClassFormatException {
                // 开发者在此自定义做字节码操作,将传入的字节码修改后返回
                // 通常这里需要字节码操作框架
                // ......
                return transformResult;
            }
        });
    }
}

步骤 2:Attach API 的程序是一个独立的 java 程序即 JVM 独立进程,需要通过 JVM 的 attach 接口与目标进程通信。

// VirtualMachine等相关Class位于JDK的tools.jar
VirtualMachine vm = VirtualMachine.attach("27082");  // 27082表示目标JVM进程pid
try {
    vm.loadAgent(".../instrumentationAgent.jar");    // 指定instrumentationAgent的jar包路径,发送给目标进程
} finally {
    vm.detach();
}

2.2 ASM

ASM 是一个 Java 字节码操作框架,用于动态生成或者增强 class。Arthas 的 watch 是基于 ASM 实现的

ASM 的工作步骤
① 通过 ClassReader 读取编译好的 .class 文件
② 通过访问者模式(Visitor)对字节码进行修改,常见的 Visitor 类有:对方法进行修改的MethodVisitor、对变量进行修改的 FieldVisitor 等
③ 通过 ClassWriter 重新构建编译修改后的字节码文件、或者将修改后的字节码文件输出到文件中

image.png
上一篇下一篇

猜你喜欢

热点阅读