Java 5 特性 Instrumentation 实践

欢迎进入Java社区论坛,与200万技术人员互动交流 >>进入

Timing 代理

在介绍完代理的基本原理之后,下文将实现一个用于测量函数运行时间的代理―― Timing。传统的函数运行时间测量代码片断为:

列表 5 传统的测量函数运行时间代码片断

    public void main(String[] args) {Long timeB = System. currentTimeMillis();             (1)methodX();System.out.print(getCurrentThreadCpuTime() - timeB); (2)}private static void methodX(){        // originial code}            

使用了代理之后,语句 (1)(2) 可以被动态的添加到类字节码中,得到等同于如下代码片断的字节码。

列表 6 与经过代理转换的字节码相对应的类文件

    public void main(String[] args) {methodX();}   private static void methodX_original (){        // originial code}private static void methodX(){        long timeB = getCurrentThreadCpuTime();        methodX_original();        Long period = System. currentTimeMillis() - timeB;        }            

列表 7 给出了Timing 代理的完整代码,其中 addTimer 方法利用 BCEL 的强大功能,动态的修改了虚拟机传递进来的类字节码。该段代码参考 developerWorks 站点文章Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码。对于 BCEL 项目的详细介绍,本文不再复述,请参阅BCEL项目的主页。

列表7 Timing 代理的完整实现

    import java.io.IOException;import java.io.ByteArrayOutputStream;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import org.apache.bcel.Constants;import org.apache.bcel.classfile.ClassParser;import org.apache.bcel.classfile.JavaClass;import org.apache.bcel.classfile.Method;import org.apache.bcel.generic.ClassGen;import org.apache.bcel.generic.ConstantPoolGen;import org.apache.bcel.generic.InstructionConstants;import org.apache.bcel.generic.InstructionFactory;import org.apache.bcel.generic.InstructionList;import org.apache.bcel.generic.MethodGen;import org.apache.bcel.generic.ObjectType;import org.apache.bcel.generic.PUSH;import org.apache.bcel.generic.Type;public class Timing implements ClassFileTransformer {    private String methodName;    private Timing(String methodName) {        this.methodName = methodName;        System.out.println(methodName);    }    public byte[] transform(ClassLoader loader, String className, Class cBR,            java.security.ProtectionDomain pD, byte[] classfileBuffer)            throws IllegalClassFormatException {        try {          ClassParser cp = new ClassParser(new java.io.ByteArrayInputStream(                    classfileBuffer), className + ".java");             JavaClass jclas = cp.parse();            ClassGen cgen = new ClassGen(jclas);            Method[] methods = jclas.getMethods();            int index;            for (index = 0; index < methods.length; index++) {                if (methods[index].getName().equals(methodName)) {                    break;                }            }            if (index < methods.length) {                addTimer(cgen, methods[index]);                ByteArrayOutputStream bos = new ByteArrayOutputStream();                cgen.getJavaClass().dump(bos);                return bos.toByteArray();            }            System.err.println("Method " + methodName + " not found in "                     + className);            System.exit(0);        } catch (IOException e) {            System.err.println(e);            System.exit(0);        }        return null; // No transformation required    }    private static void addTimer(ClassGen cgen, Method method) {        // set up the construction tools        InstructionFactory ifact = new InstructionFactory(cgen);        InstructionList ilist = new InstructionList();        ConstantPoolGen pgen = cgen.getConstantPool();        String cname = cgen.getClassName();        MethodGen wrapgen = new MethodGen(method, cname, pgen);        wrapgen.setInstructionList(ilist);        // rename a copy of the original method        MethodGen methgen = new MethodGen(method, cname, pgen);        cgen.removeMethod(method);        String iname = methgen.getName() + "_timing";        methgen.setName(iname);        cgen.addMethod(methgen.getMethod());        Type result = methgen.getReturnType();        // compute the size of the calling parameters        Type[] parameters = methgen.getArgumentTypes();        int stackIndex = methgen.isStatic() ? 0 : 1;        for (int i = 0; i < parameters.length; i++) {            stackIndex += parameters[i].getSize();        }        // save time prior to invocation        ilist.append(ifact.createInvoke("java.lang.System",            "currentTimeMillis", Type.LONG, Type.NO_ARGS,             Constants.INVOKESTATIC));        ilist.append(InstructionFactory.            createStore(Type.LONG, stackIndex));        // call the wrapped method        int offset = 0;        short invoke = Constants.INVOKESTATIC;        if (!methgen.isStatic()) {            ilist.append(InstructionFactory.                createLoad(Type.OBJECT, 0));            offset = 1;            invoke = Constants.INVOKEVIRTUAL;        }        for (int i = 0; i < parameters.length; i++) {            Type type = parameters[i];            ilist.append(InstructionFactory.                createLoad(type, offset));            offset += type.getSize();        }        ilist.append(ifact.createInvoke(cname,             iname, result, parameters, invoke));        // store result for return later        if (result != Type.VOID) {            ilist.append(InstructionFactory.                createStore(result, stackIndex+2));        }        // print time required for method call        ilist.append(ifact.createFieldAccess("java.lang.System",            "out",  new ObjectType("java.io.PrintStream"),            Constants.GETSTATIC));        ilist.append(InstructionConstants.DUP);        ilist.append(InstructionConstants.DUP);        String text = "Call to method " + methgen.getName() +            " took ";        ilist.append(new PUSH(pgen, text));        ilist.append(ifact.createInvoke("java.io.PrintStream",            "print", Type.VOID, new Type[] { Type.STRING },            Constants.INVOKEVIRTUAL));        ilist.append(ifact.createInvoke("java.lang.System",             "currentTimeMillis", Type.LONG, Type.NO_ARGS,             Constants.INVOKESTATIC));        ilist.append(InstructionFactory.            createLoad(Type.LONG, stackIndex));        ilist.append(InstructionConstants.LSUB);        ilist.append(ifact.createInvoke("java.io.PrintStream",            "print", Type.VOID, new Type[] { Type.LONG },            Constants.INVOKEVIRTUAL));        ilist.append(new PUSH(pgen, " ms."));        ilist.append(ifact.createInvoke("java.io.PrintStream",            "println", Type.VOID, new Type[] { Type.STRING },            Constants.INVOKEVIRTUAL));        // return result from wrapped method call        if (result != Type.VOID) {            ilist.append(InstructionFactory.                createLoad(result, stackIndex+2));        }        ilist.append(InstructionFactory.createReturn(result));        // finalize the constructed method        wrapgen.stripAttributes(true);        wrapgen.setMaxStack();        wrapgen.setMaxLocals();        cgen.addMethod(wrapgen.getMethod());        ilist.dispose();    }    public static void premain(String options, Instrumentation ins) {        if (options != null) {            ins.addTransformer(new Timing(options));        } else {            System.out            .println("Usage: java -javaagent:Timing.jar=\"class:method\"");             System.exit(0);        }    }}            

通过调用 Timing 代理,当运行结束之后,被检测类的字节码不会改动。函数运行时间的检测,是通过运行期间,动态的插入函数,并且改变调用序列来实现的。图3给出了使用命令行 java -javaagent:Timing.jar=”helloWorld” Sample 运行代理 Timing 的结果。

列表 8 通过命令行参数调用代理

                java -javaagent:Timing.jar="helloWorld" Sample

图3 运行代理 Timing 的结果

资源 Timing.jar 文件将包含 Timing 代理的源代码和类文件,以及使用说明。

[1][2]

曾经拥有的不要忘记,难以得到的更要珍惜,

Java 5 特性 Instrumentation 实践

相关文章:

你感兴趣的文章:

标签云: