java.lang.Instrument 代理Agent使用

java.lang.Instrument包是在JDK5引入的,程序员通过修改方法的字节码实现动态修改类代码。这通常是在类的main方法调用之前进行预处理的操作,通过java指定该类的代理类来实现。

(1) 代理 (agent) 是在你的main方法前的一个拦截器 (interceptor),也就是在main方法执行之前,执行agent的代码。
agent的代码与你的main方法在同一个JVM中运行,并被同一个system classloader装载,被同一的安全策略 (security policy)上下文 (context)所管理。
代理(agent)这个名字有点误导的成分,它与我们一般理解的代理不大一样。java agent使用起来比较简单。怎样写一个java agent? 只需要实现premain这个方法:
public static void premain(String agentArgs, Instrumentation inst)
JDK 6 中如果找不到上面的这种premain的定义,还会尝试调用下面的这种premain定义:
public static void premain(String agentArgs)

(2)Agent 类必须打成jar包,然后里面的META-INF/MAINIFEST.MF,必须包含Premain-Class这个属性。
下面是一个MANIFEST.MF的例子:

Manifest-Version: 1.0
Premain-Class:MyAgent1
Created-By:1.6.0_06

然后把MANIFEST.MF加入到你的jar包中。以下是agent jar文件的Manifest Attributes清单:
Premain-Class
如果 JVM 启动时指定了代理,那么此属性指定代理类,即包含 premain 方法的类。如果 JVM 启动时指定了代理,那么此属性是必需的。如果该属性不存在,那么 JVM 将中止。注:此属性是类名,不是文件名或路径。
Agent-Class
如果实现支持 VM 启动之后某一时刻启动代理的机制,那么此属性指定代理类。 即包含 agentmain 方法的类。 此属性是必需的,如果不存在,代理将无法启动。注:这是类名,而不是文件名或路径
Boot-Class-Path
设置引导类加载器搜索的路径列表。路径表示目录或库(在许多平台上通常作为 JAR 或 zip 库被引用)。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。此属性是可选的。
Can-Redefine-Classes
布尔值(true 或 false,与大小写无关)。是否能重定义此代理所需的类。true 以外的值均被视为 false。此属性是可选的,默认值为 false。
Can-Retransform-Classes
布尔值(true 或 false,与大小写无关)。是否能重转换此代理所需的类。true 以外的值均被视为 false。此属性是可选的,默认值为 false。
Can-Set-Native-Method-Prefix
布尔值(true 或 false,与大小写无关)。是否能设置此代理所需的本机方法前缀。true 以外的值均被视为 false。此属性是可选的,默认值为 false。

(3)所有的这些Agent的jar包,都会自动加入到程序的classpath中。所以不需要手动把他们添加到classpath。除非你想指定classpath的顺序。

(4)一个java程序中-javaagent这个参数的个数是没有限制的,所以可以添加任意多个java agent。所有的java agent会按照你定义的顺序执行。
例如:

java -javaagent:MyAgent1.jar -javaagent:MyAgent2.jar -jar MyProgram.jar

假设MyProgram.jar里面的main函数在MyProgram中。MyAgent1.jar, MyAgent2.jar, 这2个jar包中实现了premain的类分别是MyAgent1, MyAgent2
程序执行的顺序将会是:

MyAgent1.premain -> MyAgent2.premain -> MyProgram.main

(5)另外,放在main函数之后的premain是不会被执行的,例如:

java -javaagent:MyAgent1.jar -jar MyProgram.jar -javaagent:MyAgent2.jar

MyAgent2 都放在了MyProgram.jar后面,所以MyAgent2的premain都不会被执行,所以执行的结果将是:

MyAgent1.premain -> MyProgram.main

(6)每一个java agent 都可以接收一个字符串类型的参数,也就是premain中的agentArgs,这个agentArgs是通过java option中定义的。例如:

java -javaagent:MyAgent2.jar=thisIsAgentArgs -jar MyProgram.jar

MyAgent2中premain接收到的agentArgs的值将是”thisIsAgentArgs” (不包括双引号)。

(7)参数中的Instrumentation:通过参数中的Instrumentation inst,添加自己定义的ClassFileTransformer,来改变class文件。这里自定义的Transformer实现了transform方法,在该方法中提供了对实际要执行的类的字节码的修改,甚至可以达到执行另外的类方法的地步。例如:
写agent类

import java.lang.instrument.Instrumentation;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
public class PerfMonAgent {
    private static Instrumentation inst = null;
    /**
     * This method is called before the application’s main-method is called,
     * when this agent is specified to the Java VM.
     **/
    public static void premain(String agentArgs, Instrumentation _inst) {
        System.out.println("PerfMonAgent.premain() was called.");
        // Initialize the static variables we use to track information.
        inst = _inst;
        // Set up the class-file transformer.
        ClassFileTransformer trans = new PerfMonXformer();
        System.out.println("Adding a PerfMonXformer instance to the JVM.");
        inst.addTransformer(trans);
    }
}

写ClassFileTransformer类

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
public class PerfMonXformer implements ClassFileTransformer {
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] transformed = null;
        System.out.println("Transforming " + className);
        ClassPool pool = ClassPool.getDefault();
        CtClass cl = null;
        try {
            cl = pool.makeClass(new java.io.ByteArrayInputStream(
                    classfileBuffer));
            if (cl.isInterface() == false) {
                CtBehavior[] methods = cl.getDeclaredBehaviors();
                for (int i = 0; i < methods.length; i++) {
                    if (methods[i].isEmpty() == false) {
                        doMethod(methods[i]);
                    }
                }
                transformed = cl.toBytecode();
            }
        } catch (Exception e) {
            System.err.println("Could not instrument  " + className
                    + ",  exception : " + e.getMessage());
        } finally {
            if (cl != null) {
                cl.detach();
            }
        }
        return transformed;
    }
    private void doMethod(CtBehavior method) throws NotFoundException,
            CannotCompileException {
        // method.insertBefore("long stime = System.nanoTime();");
        // method.insertAfter("System.out.println(\"leave "+method.getName()+" and time:\"+(System.nanoTime()-stime));");
        method.instrument(new ExprEditor() {
            public void edit(MethodCall m) throws CannotCompileException {
                m.replace("{ long stime = System.nanoTime(); $_ = $proceed($$); System.out.println(\""
                                + m.getClassName()+"."+m.getMethodName()
                                + ":\"+(System.nanoTime()-stime));}");
            }
        });
    }
}

上面两个类就是agent的核心了,jvm启动时并会在应用加载前会调用 PerfMonAgent.premain,然后PerfMonAgent.premain中实例化了一个定制的ClassFileTransforme即 PerfMonXformer,并通过inst.addTransformer(trans);把PerfMonXformer的实例加入Instrumentation实例(由jvm传入),这就使得应用中的类加载的时候, PerfMonXformer.transform都会被调用,你在此方法中可以改变加载的类,真的有点神奇,为了改变类的字节码,我使用了jboss的javassist,虽然你不一定要这么用,但jboss的javassist真的很强大,让你很容易的改变类的字节码。

在上面的方法中我通过改变类的字节码,在每个类的方法入口中加入了long stime = System.nanoTime();,在方法的出口加入了System.out.println(“methodClassName.methodName:“+(System.nanoTime()-stime));

时间: 2024-10-19 23:14:49

java.lang.Instrument 代理Agent使用的相关文章

J2SE 1.6 特性:java.lang.instrument

1. import java.lang.instrument.Instrumentation; public class ObjectSizeFetcher { private static Instrumentation instrumentation; public static void premain(String args, Instrumentation inst) { instrumentation = inst; } public static long getObjectSiz

java.lang包

作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/ 1.特性——不用import 2.String String x = "abc"; <=> String x= new String("abc"); 因为public final class java.lang.String; 而String x="The number " + y;中,在JAVA中不管是什么变量或者对象,在对String进行加和时

java.lang.instument包超详解

1 java.lang.instrument简介 Java5之后,增加了一个包java.lang.instrument,这个包的东西很少,两个接口,ClassFileTransformer和Instrumentation,一个类ClassDefinition,还有两个Exception:IllegalClassFormatException和UnmodifiableClassException: 先剧透一下整个包最重要的接口Instrumentation提供的两个功能,总体的功能就是Instru

jdk研究——java.lang

jdk研究 volatile 是什么意思? 如何看jdk源码? 如何调试源码!---------仔细解读关键类,关键代码,常用的api的解释! 自己有疑问的不懂地方-------- 不懂的太多怎么办.... 求分享求带求讲解原理啊! 有老师还是比没有好得多! 关键代码.难懂代码是哪些啊! 承上启下 结构图?流水图? 哪些又是胶水代码呢.辅助代码 PACKAGE java.lang Object System 大量出现类似:SecurityManager sm = getSecurityManag

自己写一个java.lang.reflect.Proxy代理的实现

前言 Java设计模式9:代理模式一文中,讲到了动态代理,动态代理里面用到了一个类就是java.lang.reflect.Proxy,这个类是根据代理内容为传入的接口生成代理用的.本文就自己写一个Proxy类出来,功能和java.lang.reflect.Proxy一样,传入接口.代理内容,生成代理. 抛砖引玉吧,个人觉得自己写一些JDK里面的那些类挺好的,写一遍和看一遍真的是两个不同的概念,写一遍既加深了对于这些类的理解.提升了自己的写代码水平,也可以在写完之后对比一下自己的实现有哪些写得不好

Cassandra 2.x 提示“错误: 代理抛出异常错误: java.lang.NullPointerException”

这个问题多半是由于运行了多个Cassandra实例造成的错误,看cassandra的启动脚本中可发现这样的语句: # see CASSANDRA-7254 "$JAVA" -cp $CLASSPATH $JVM_OPTS 2>&1 | grep -q 'Error: Exception thrown by the agent : java.lang.NullPointerException' if [ $? -ne "1" ]; then echo U

关于数据库连接池使用代理报 java.lang.ClassCastException

用到动态代理时会发生这样的错误: java.lang.ClassCastException: $Proxy0 cannot be cast to java.sql.Connection 原因是数据库驱动版本的问题: 当用到: mysql-connector-java-5.0.8-bin.jar 则不会出报错: 但是,当用到: mysql-connector-java-5.1.7-bin.jar (5.1以上) 则会报 java.lang.ClassCastException: $Proxy0 c

关于利用动态代理手写数据库连接池的异常 java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to java.sql.Connection

代码如下: final Connection conn=pool.remove(0); //利用动态代理改造close方法 Connection proxy= (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object pro

Core Java:使用java.lang.reflect实现JDK动态代理的小DEMO

代理模式在Java的体系结构中具有非常重要的地位,包括Spring的整个庞大的IOC体系都是建立在代理模式之上,而AOP也是在代理模式的基础上发展起来,增加了触发动作行为的时机.因此掌握代理模式是非常必要的技能.    下面通过一个简单的DEMO,来探究一下java.lang.reflect是如何实现代理模式的,重点在main方法上,我们明明为flyImpl实现类的wing(String)赋值为null,但是最终的输出结果中,却发现这个flyImpl对象的fly域变成了"A injected s