BTrace实现原理

BTrace是每个Java程序员必备的瑞士军刀,可以实现线上服务器不重启增加调试信息。本文简单介绍一下其实现原理。

BTrace工作原理

BTrace是基于动态字节码修改技术(Hotswap)来实现运行时java程序的跟踪和替换。大体的原理可以用下面的公式描述:

Client(Java compile api + attach api) + Agent(脚本解析引擎 + ASM + Instumentation) + Socket

BTrace工作序列图

BTrace实现原理_第1张图片
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功能上非常简单,仅提供了如下几个功能:

  1. 列出当前所有的JVM实例描述
  2. Attach到其中一个JVM上,建立通信管道
  3. 让目标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);
}

BTrace系列

  • BTrace常用场景示例

参考

  • 动态追踪技术漫谈
  • Instrumentation 新功能
  • BTrace 原理浅析
  • Java Attach API
  • 编译器 API
  • Java ASM介绍

你可能感兴趣的:(BTrace实现原理)