【性能优化】Arthas 原理浅谈
Arthas 是基于 ASM 和 Java 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。
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
,在类加载的时候更新对应的类的字节码。
image.pngpremain 方法的执行步骤如下:
① 创建InstrumentationImpl
对象
② 监听ClassFileLoadHook
事件
③ 调用InstrumentationImpl
的loadClassAndCallPremain
方法,最终调用 javaagent 里 MANIFEST.MF 里指定的Premain-Class
类的premain
方法
方式2:Attach API 运行时的加载方式
Attach API 在运行时进行类的字节码的修改,关键是注册类的 TransFormer、调用 retransformClasses 函数重加载类。
image.pngagentmain 方法的执行步骤如下:
① 创建InstrumentationImpl
对象
② 监听ClassFileLoadHook
事件
③ 调用InstrumentationImpl
的loadClassAndCallAgentmain
方法,最终调用 javaagent 里 MANIFEST.MF 里指定的Agentmain-Class
类的agentmain
方法
2.1.2 Java Instrumentation 介绍
Instrumentation 是一个 JVM 接口,该接口提供了一组查看和操作 Java 类的方法,例如修改类的字节码、向 classLoader 的 classpath 下加入 jar 文件等。开发者可以通过 Java 语言来操作和监控 JVM 内部的状态,进而实现 Java 程序的监控分析。Instrumentation 整体的执行流程可以参考文章 Java 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 重新构建编译修改后的字节码文件、或者将修改后的字节码文件输出到文件中