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

构建自己的监测器【6】-agentmain方式 博客分类: 编程开发 性能监控 我的源码

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

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

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

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

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

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

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

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

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

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

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

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

下面是具体的实现:

首先是具体的应用类:

Java代码 复制代码 收藏代码
  1. /**
  2. * TODO Comment of VMTest
  3. * @author yongkang.qiyk
  4. */ 
  5. public class VMTest { 
  6.     public static void main(String[] args) throwsInterruptedException { 
  7.         while(true){ 
  8.             Thread.sleep(10000); 
  9.             new Thread(new WaitThread()).start(); 
  10.         } 
  11.     } 
  12.      
  13.    static class WaitThread implements Runnable { 
  14.   
  15.         @Override 
  16.         public void run() { 
  17.             try
  18.                 WaitTest w = new WaitTest(); 
  19.                 w.waiting(); 
  20.             } catch (InterruptedException e) { 
  21.                 // TODO Auto-generated catch block 
  22.                 e.printStackTrace(); 
  23.             } 
  24.         } 
  25.          
  26.     } 
/**
 * 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类:

Java代码 复制代码 收藏代码
  1. public class MyAgentMain { 
  2.      
  3.     public static void agentmain(String args, Instrumentation inst)throws UnmodifiableClassException{ 
  4.         System.out.println("MyAgentMain agentmain attach..."); 
  5.          
  6.         System.getProperties().setProperty("monitor.conf", args); 
  7.          
  8.         for (Class clazz :inst.getAllLoadedClasses()){ 
  9.            System.out.println(clazz.getName());  
  10.         } 
  11. //        inst.addTransformer(newMonitorAgentTransformer(),true); 
  12. //       inst.retransformClasses(WaitTest.class); 
  13.          
  14.         System.out.println("MyAgentMain agentmain end..."); 
  15.     } 
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的包之后,写一个在运行时加载代理的实现:

Java代码 复制代码 收藏代码
  1. public class VirtualMachineTest { 
  2.   
  3.     public static void main(String[] args) throwsAttachNotSupportedException, IOException, 
  4.             AgentLoadException,AgentInitializationException, InterruptedException { 
  5.         // attach to target VM  
  6.         VirtualMachine vm = VirtualMachine.attach("13712"); 
  7.         vm.loadAgent("D:/tools/java/monitor.jar", "D:/tools/java/profile.txt"); 
  8.         Thread.sleep(1000); 
  9.         vm.detach(); 
  10.     } 
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,通过AttachToolsAPI的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应该基本满足我们的需要了,如果一定要在运行时添加方法,删除方法等,也一定有方法可以绕开jdk6instrumentation的这个限制。会找到办法的。。。。

你可能感兴趣的:(Java)