zoukankan      html  css  js  c++  java
  • Java 5 特性 Instrumentation 实践【文摘】

    转自:https://www.ibm.com/developerworks/cn/java/j-lo-instrumentation/

    简介: Instrumentation 是 Java 5 提供的新特性。使用 Instrumentation,开发者可以构建一个代理,用来监测运行在 JVM 上的程序。监测一般是通过在执行某个类文件之前,对该类文件的字节码进行适当修改进行的。下文将通过一个具体的例子,来展示 java.lang.instrument 包的工作原理,并且实现了一个测量函数运行时间的代理。

    不使用instrumentation 来测量函数运行时间的传统方法是:在函数调用之前记录当前系统时间,在函数调用完成之后再次记录当前系统时间(为了简化描述,本文不考虑虚拟机进程映射到本地操作系统进程时造成的计时误差,详见Use the JVM Profiler Interface for accurate timing)。最后将两次数据的差值作为本次函数运行时间返回。这种方法的弱点在于:

    • 用于性能测量的语句直接夹杂在逻辑代码中
    • 用于性能测量的逻辑是重复的,没有做到代码重用。

    使用 instrumentation 提供的功能,结合 Apache 开源项目 BCEL,本文将实现一个用于测量函数运行时间的代理。通过代理技术,用于性能测量的语句与业务逻辑完全分离,同时该代理可以用于测量任意类的任意方法的运行时间,大大提高了代码的重用性。

    Greeting 代理
    在实现函数运行时间测量代理之前,我们先通过实现一个简单的 Greeting 代理,介绍一下 Java 5 中 instrumentation 的原理。每个代理的实现类必须实现 ClassFileTransformer 接口。这个接口提供了一个方法:

    public byte[] transform(
        ClassLoader loader, 
        String className, 
        Class cBR, 
        java.security.ProtectionDomain pD, 
        byte[] classfileBuffer) throws IllegalClassFormatException

    通过这个方法,代理可以得到虚拟机载入的类的字节码(通过 classfileBuffer 参数)。代理的各种功能一般是通过操作这一串字节码得以实现的。同时还需要提供一个公共的静态方法:

    public static void premain(String agentArgs, Instrumentation inst)

    一般会在这个方法中创建一个代理对象,通过参数 inst 的 addTransformer() 方法,将创建的代理对象再传递给虚拟机。这个方法是一个入口方法,有点类似于一般类的 main 方法。图1展示了代理工作的原理:

    图1 代理工作原理

    可以看到,多个代理可以同时执行。这多个代理的 premain 方法将按照代理指定的顺序被依次调用。

    下面的代码片断,演示了 Greeting 代理的 transform 方法。在该方法中我们对 agent 的行为进行了简单的定制——输出需要该代理监测的类名。

    列表1 输出 Hello, someClass

    //输出 Hello, someClass
    public
    byte[] transform(ClassLoader loader, String className, Class cBR, java.security.ProtectionDomain pD, byte[] classfileBuffer) throws IllegalClassFormatException{ System.out.println("Hello,\t" + className); return null; }

    transform 函数的最后,返回 null 值,表示不需要进行类字节码的转化。定制完代理的行为之后,创建一个 greeting 代理的实例,将该实例传递给虚拟机。

    列表2 将 Greeting 代理的实例传递给虚拟机

    // 将 Greeting 代理的实例传递给虚拟机
    public
    static void premain(String options, Instrumentation ins) { if (options != null) { System.out.printf(" I've been called with options: \"%s\"\n", options); }else{ System.out.println(" I've been called with no options.");
    } ins.addTransformer(
    new Greeting()); }

    options 参数是通过命令行传递进来的,类似于调用 main 函数时传递的参数。被传递进来的命令行参数是一个完整的字符串,不同于 main 方法,该字符串的解析完全由代理自己负责。列表 3 展示了如何使用命令行调用代理:

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

    java -javaagent:Greeting.jar="Hello, Sample" Sample

    这条命令表示,用参数”Hello, Sample”调用 Greeting 代理,以检测 Sample 类的运行情况。运行该命令之后的结果如下图:

    图2 运行代理 Greeting 的结果

    代理需要被打包到一个符合特定标准的 jar 文件中运行。该 jar 文件的 MANIFEST.MF 文件需要包括一些特殊的项以定义代理类等信息。(请查阅 Java 5 规约,获取详细信息)在列表 4 中,我们指定了 Greeting 代理的代理类是 Greeting.class。

    列表4 Greeting 代理的 MANIFEST.MF 文件

    Manifest-Version: 1.0
    Premain-Class: Greeting

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

    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 代理的源代码和类文件,以及使用说明。

  • 相关阅读:
    发布自己的包到Nuget上
    asp.net core 中的MD5加密
    asp.net core csrf
    KNN算法
    ios测试apk
    python多进程
    机顶盒 gettimeofday()获取毫秒溢出
    Kiggle:Digit Recognizer
    Kaggle:Titanic: Machine Learning from Disaster
    Python抓取微博评论
  • 原文地址:https://www.cnblogs.com/zhangqingsh/p/2980840.html
Copyright © 2011-2022 走看看