构建自己的监测器【6】-agentmain方式

来源:互联网 发布:淘宝差评对卖家的影响 编辑:程序博客网 时间:2024/05/18 21:49

在前面的一些例子中,已经用到了jdk5中premain和instrumentation的一些基本用法,给了我们很多惊喜,相当的给力。不过也有一些不方便的地方,由于其必须在命令行指定代理jar,并且代理类必须在main方法前启动。因此,要求开发者在应用前就必须确认代理的处理逻辑和参数内容等等,在有些场合下,这是比较困难的。比如正常的生产环境下,一般不会开启代理功能,但是在发生问题时,我们不希望停止应用就能够动态的去修改一些类的行为,以帮助排查问题,这在应用启动前是无法确定的。 为解决运行时启动代理类的问题,Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。 与Permain类似,agent方式同样需要提供一个agent jar,并且这个jar需要满足:

  1. 在manifest中指定Agent-Class属性,值为代理类全路径

  2. 代理类需要提供public static void agentmain(String args, Instrumentation inst)或public static void agentmain(String args)方法。并且再二者同时存在时以前者优先。args和inst和premain中的一致。

假如上面的agent的jar包打好了,那面临另外一个问题——如何在应用程序启动之后再开启代理程序呢? JDK6中提供了Java Tools API,其中Attach API可以满足这个需求。

在Attach API中的VirtualMachine代表一个运行中的VM。其提供了loadAgent()方法,可以在运行时动态加载一个代理jar。

Attach API在%java_home%\lib这个目录下的tool.jar包中。我是将它打在工程的lib包中的。

Attact API主要在这个目录下,也就这些东西,如图:


最主要的类就是VirtualMachine,下面写个例子熟悉下它。

这个例子做的事情很简单:

1、一个VMTest类没1秒钟输出一行:System.out.println("wait.....");  

2、写一个MyAgentMain类,这个类实现了

public static void agentmain(Stringargs, Instrumentation inst) 方法,在这个方法中打印出所有已经装在的class,其他什么也不做(也可以像先前讲解premin方法中的加入方法耗时统计)。


下面是具体的实现:

首先是具体的应用类:

/** * TODO Comment of VMTest * @author yongkang.qiyk */public class VMTest {    public static void main(String[] args) throwsInterruptedException {        while(true){            Thread.sleep(10000);            new Thread(new WaitThread()).start();        }    }       static class WaitThread implements Runnable {         @Override        public void run() {            try {                WaitTest w = new WaitTest();                w.waiting();            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }            }}


这个是首先运行的类,运行后,每10秒打印一次 System.out.println("wait.....");


然后编写我们的运行时要加载的agent类:

public class MyAgentMain {        public static void agentmain(String args, Instrumentation inst)throws UnmodifiableClassException{        System.out.println("MyAgentMain agentmain attach...");                System.getProperties().setProperty("monitor.conf", args);                for (Class clazz :inst.getAllLoadedClasses()){           System.out.println(clazz.getName());         }//        inst.addTransformer(newMonitorAgentTransformer(),true);//       inst.retransformClasses(WaitTest.class);                System.out.println("MyAgentMain agentmain end...");    }}


可以看到,这个agent类只是打印了加载的类,以及运行时设置了一个系统属性,这个系统属性是监控配置的文件路径,在这个例子中我没有用到它。


上面的写好之后,记得打成jar包,。注意:METAINF.MF的中记得加入Agent-Class:这个属性:


Manifest-Version: 1.0

Premain-Class: monitor.agent.MyAgent

Agent-Class: monitor.agentmain.MyAgentMain

Can-Redefine-Classes: true

Can-Retransform-Classes:true

Boot-Class-Path: javassist.jar

 


打包好monitor.jar的包之后,写一个在运行时加载代理的实现:

public class VirtualMachineTest {     public static void main(String[] args) throwsAttachNotSupportedException, IOException,            AgentLoadException,AgentInitializationException, InterruptedException {        // attach to target VM         VirtualMachine vm = VirtualMachine.attach("13712");        vm.loadAgent("D:/tools/java/monitor.jar", "D:/tools/java/profile.txt");        Thread.sleep(1000);        vm.detach();    }}


可以这个类做的事情也很简单,该程序接受一个参数为目标应用程序的进程id,通过Attach Tools API的VirtualMachine.attach方法绑定到目标VM,并向其中加载代理jar。13712就是刚才我运行VMTest的进程pid。

运行VirtualMachineTest 可以看到运行结果:


   打印出了所有已经加载的class。


同样的,你也可以通过instrumentation的 inst.addTransformer()方法加入ClassFileTransformer的实现类,然后对指定的class进行修改。

但是在运行时对class修改和premain的方式对class的修改是有点差别的,至少在jdk6以及以前的版本是这样的。

通过premain的方式在应用启动前对class可以做任何修改,可以添加class,删除class,添加删除修改方法体,添加成员变量等等没有任何限制,

然而通过agentmain的方式在运行时修改class是有限制的,,比如在class已经被加载过的情况下,是不能对class添加,删除方法的,只能重定义方法体,这对很多功能其实做了限制。相信jdk6以后的版本应该会放开这个限制吧。

但是我个人感觉,通过premain这种方式在vm启动前修改好class应该基本满足我们的需要了,如果一定要在运行时添加方法,删除方法等,也一定有方法可以绕开jdk6 instrumentation的这个限制。会找到办法的。。。。

参考文档:http://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html 

http://www.jar114.com/jdk6/zh_CN/api/java/lang/instrument/package-summary.html 


原创粉丝点击