Btrace系列之一:Btrace的基本原理

 

写在前面的话:Btrace系列是我将平时里学习和使用Btrace的一些经验的总结,拿出来和大家一起交流一下,希望在这个过程中能找寻出自己理解或使用上的错误之处。

 

一、Btrace的简介:

    Btrace是由Kenai 开发的一个开源项目,是一种动态跟踪分析JAVA源代码的工具。它可以用来帮我们做运行时的JAVA程序分析,监控等等操作,当然,它也不是万能的,BTrace也有一些使用上的限制,如:不能在脚本中新建类等等,这些在官方网站上有很详细的介绍,大家有兴趣可以查看:http://kenai.com/projects/btrace/pages/UserGuide。

 

二、JDK6的几个新特性:

     Btrace是由:Attach API + BTrace脚本解析引擎 + ASM + JDK6 Instumentation组成,这里要注意最后一项是JDK6的Instumentation,为什么一定要是JDK6呢?我们就要来看一下JDK6为我们提供了什么:

1、虚拟机启动后的Instumentation:

        Instumentation早在JDK5的时候就已经提出了,但是它有个局限性,premain函数只能在main函数之前被运行(对于Instumentation不熟悉的请去Sun官网查看),而JDK6之后提供了一个叫做agentmain的函数,它可以在main函数运行后在运行,代码如下:

public static void agentmain(String args, Instrumentation inst)
public static void agentmain(String args)

 这个函数的功能通premain函数一样,可以对类进行各种操作。同premain函数一样,在manifest 文件里面设置“Agent-Class”来指定包含 agentmain 函数的类。

2、Instumentation提供的新方法retransformClasses:

        这个新方法可以在agentmain函数中调用,它的功能和redefineClasses 一样,可以修改类的定义且是批量的。        

3、BootClassPath和SystemClassPath的动态指定:

       在JDK6之前,我们知道可以通过系统参数或者虚拟机启动参数,设置一个虚拟机运行时的boot class加载路径和system class加载路径,但是在启动后,这个路径是不可以修改的,并且,我们要在启动后再去加载一个*.jar文件是不可能的,但是在JDK6以后,这个规定被打破了,可以使用Instrumentation的appendToBootstrapClassLoaderSearch和appendToSystemClassLoaderSearch来修改路径或加载新的*.jar(注意:虽然实际的classpath被修改了,但是在property中的java.class.path却没有受任何影响)。

 

正因为以上的新特性,才缔造出了Btrace的想法以至于最后的实现。

 

三、Btrace的原理:

Btrace首先是通过Attach API中提供的VirtualMachine.attach(PID)方法来获得要监控的JVM,然后使用VirtualMachine.loadAgent("*.jar")方法来加载jar文件,这个jar文件中会包含Btrace的一个很重要的类com.sun.btrace.agent.Main,这个类里定义了如下的函数:

public static void premain(String args, Instrumentation inst) {
        main(args, inst);
 }

public static void agentmain(String args, Instrumentation inst) {
       main(args, inst);
}

 这里两个函数都调用了一个main方法,如下:

private static synchronized void main(final String args, final Instrumentation inst) {
......
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(path)));
......
inst.appendToSystemClassLoaderSearch(new JarFile(new File(path)));
......
startServer();
}

 这里省去了不必要的代码,主要三行代码,头两行不用解释,上面已经说过用途了,第三行解释一下,代码如下:

private static void startServer() {
......
while (true) {
            try {
......
                handleNewClient(client);
            } catch (RuntimeException re) {
                if (isDebug()) debugPrint(re);
            } catch (IOException ioexp) {
                if (isDebug()) debugPrint(ioexp);
            }
        }
}

 关键看一下handleNewClient(client)方法的调用,这个是修改类定义的地方,如下:

private static void handleNewClient(final Client client) {
......
inst.addTransformer(client, true);
......
inst.retransformClasses(classes);
}

 这两句话就实现了对现有内存中的类定义的替换,当在一次调用new创建一个新对象时就会使用新的类定义,而老的已经生成的类对象是不会收到干扰的。

那又是谁取执行了对类定义的修改呢,这个是由BTrace脚本解析引擎 + ASM来实现的,脚本引擎负责解析我们所写的脚本,而ASM来对JAVA的字节码进行增强修改。你在Btrace的com.sun.btrace.agent.Client中可以看到ASM的影子,代码如下:

abstract class Client implements ClassFileTransformer, CommandListener {
static {
        ClassFilter.class.getClass();
        ClassReader.class.getClass();
        ClassWriter.class.getClass();
......
    }

 private byte[] instrument(Class clazz, String cname, byte[] target) {
        byte[] instrumentedCode;
        try {
            ClassWriter writer = InstrumentUtils.newClassWriter(target);
            ClassReader reader = new ClassReader(target);
            Instrumentor i = new Instrumentor(clazz, className,  btraceCode, onMethods, writer);
......
    }

 现在我们在回顾一下整个流程,用Attach API附加*.jar然后使用BTrace脚本解析引擎 + ASM来实现对类的修改,在使用Instumentation实现类的内存替换,完毕!perfect!BTrace原来也就这么回事,但是我们不得不佩服开发团队的思维和整合能力,向牛人致敬!

你可能感兴趣的:(jvm,虚拟机,脚本,sun)