BTrace是每个Java程序员必备的瑞士军刀,可以实现线上服务器不重启增加调试信息。本文简单介绍一下其实现原理。
BTrace工作原理
BTrace是基于动态字节码修改技术(Hotswap)来实现运行时java程序的跟踪和替换。大体的原理可以用下面的公式描述:
Client(Java compile api + attach api) + Agent(脚本解析引擎 + ASM + Instumentation) + Socket
BTrace工作序列图
BTrace工作序列图Java Compile API
BTrace使用Compile API把用户编写的源码文件编译成字节码文件
在 JDK 6 中,类库通过 javax.tools 包提供了程序运行时调用编译器的 API。
public class Compiler {
public static void main(String[] args) throws Exception {
String fullQuanlifiedFileName = "compile" + java.io.File.separator + "Target.java";
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
FileOutputStream err = new FileOutputStream("err.txt");
int compilationResult = compiler.run(null, null, err, fullQuanlifiedFileName);
if (compilationResult == 0) {
System.out.println("Done");
} else {
System.out.println("Fail");
}
}
}
Java Attach API
BTrace使用Attach API把BTrace Agent附加到待调试的JVM上去
Attach API不是Java的标准API,而是Sun公司提供的一套扩展API,用来向目标JVM"附着"(Attach)代理工具程序的。有了它,开发者可以方便的监控一个JVM,运行一个外加的代理程序,Sun JVM Attach API功能上非常简单,仅提供了如下几个功能:
- 列出当前所有的JVM实例描述
- Attach到其中一个JVM上,建立通信管道
- 让目标JVM加载Agent
ASM
BTrace使用ASM修改当前类,附加调试信息,得到新的类
我们都知道,一般情况下,Class文件是通过javac编译器产生的,然后通过类加载器加载到虚拟机内,再通过执行引擎去执行。现在我们可以通过ASM的API直接生成符合Java虚拟机规范的Class字节流,这样,ASM做的事情一定程度上正是javac解释器做的工作。
可以说ASM分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件。
Instrumentation
BTrace使用JVM Instrumentation动态替换当前类
利用 Java 代码,即 java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
关键逻辑
BTrace的入口类在https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/client/Main.java中。在其main方法中,可以看到起最终的核心逻辑是在https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/client/Client.java中。方法调用如下:
- client.compile
- client.attach
- client.submit
核心流程代码如下:
try {
Client client = new Client(port, OUTPUT_FILE, PROBE_DESC_PATH,
DEBUG, TRACK_RETRANSFORM, TRUSTED, DUMP_CLASSES, DUMP_DIR, statsdDef);
if (! new File(fileName).exists()) {
errorExit("File not found: " + fileName, 1);
}
//编译Java源码
byte[] code = client.compile(fileName, classPath, includePath);
if (code == null) {
errorExit("BTrace compilation failed", 1);
}
if (!hostDefined)
//调用JVM的Attach API,在被调试JVM中attach一个Agent
client.attach(pid, null, classPath);
registerExitHook(client);
if (con != null) {
registerSignalHandler(client);
}
if (isDebug()) debugPrint("submitting the BTrace program");
//提交调试代码(被调试JVM中的Agent收到代码后使用ASM修改类定义)
client.submit(host, fileName, code, btraceArgs, createCommandListener(client));
} catch (IOException exp) {
errorExit(exp.getMessage(), 1);
}
网友评论