java instrument 初探

原文地址:http://blog.csdn.net/pwlazy/article/details/5109742


java在1.5引入java.lang.instrument,你可以由此实现一个java agent,通过此agent来修改类的字节码即改变一个类。本文中,会通过java instrument 实现一个简单的profiler。当然instrument并不限于profiler,instrument可以做很多事情,

它类似一种更低级,更松耦合的AOP,可以从底层来改变一个类的行为,你可以由此产生无限的遐想。

接下来要做的事情,就是计算一个方法所花的时间,通常我们会在代码这么写: 
在方法开始开头加入long stime = System.nanoTime();
在方法结尾通过System.nanoTime()-stime得出方法所花时间,你不得不在你想监控的每个方法中写入重复的代码,
好一点的情况,你可以用AOP来干这事,但总是感觉有点别扭,这种profiler的代码还是打包在你的项目中,

java instrument使得这更干净。

 

1) 写agent类

[java]  view plain copy
  1. package org.toy;  
  2. import java.lang.instrument.Instrumentation;  
  3. import java.lang.instrument.ClassFileTransformer;  
  4. public class PerfMonAgent {  
  5.     static private Instrumentation inst = null;  
  6.     /** 
  7.      * This method is called before the application’s main-method is called, 
  8.      * when this agent is specified to the Java VM. 
  9.      **/  
  10.     public static void premain(String agentArgs, Instrumentation _inst) {  
  11.         System.out.println("PerfMonAgent.premain() was called.");  
  12.         // Initialize the static variables we use to track information.  
  13.         inst = _inst;  
  14.         // Set up the class-file transformer.  
  15.         ClassFileTransformer trans = new PerfMonXformer();  
  16.         System.out.println("Adding a PerfMonXformer instance to the JVM.");  
  17.         inst.addTransformer(trans);  
  18.     }  
  19. }  

 

2)写ClassFileTransformer类
 

[java]  view plain copy
  1. package org.toy;  
  2. import java.lang.instrument.ClassFileTransformer;  
  3. import java.lang.instrument.IllegalClassFormatException;  
  4. import java.security.ProtectionDomain;  
  5. import javassist.CannotCompileException;  
  6. import javassist.ClassPool;  
  7. import javassist.CtBehavior;  
  8. import javassist.CtClass;  
  9. import javassist.NotFoundException;  
  10. import javassist.expr.ExprEditor;  
  11. import javassist.expr.MethodCall;  
  12. public class PerfMonXformer implements ClassFileTransformer {  
  13.     public byte[] transform(ClassLoader loader, String className,  
  14.             Class<?> classBeingRedefined, ProtectionDomain protectionDomain,  
  15.             byte[] classfileBuffer) throws IllegalClassFormatException {  
  16.         byte[] transformed = null;  
  17.         System.out.println("Transforming " + className);  
  18.         ClassPool pool = ClassPool.getDefault();  
  19.         CtClass cl = null;  
  20.         try {  
  21.             cl = pool.makeClass(new java.io.ByteArrayInputStream(  
  22.                     classfileBuffer));  
  23.             if (cl.isInterface() == false) {  
  24.                 CtBehavior[] methods = cl.getDeclaredBehaviors();  
  25.                 for (int i = 0; i < methods.length; i++) {  
  26.                     if (methods[i].isEmpty() == false) {  
  27.                         doMethod(methods[i]);  
  28.                     }  
  29.                 }  
  30.                 transformed = cl.toBytecode();  
  31.             }  
  32.         } catch (Exception e) {  
  33.             System.err.println("Could not instrument  " + className  
  34.                     + ",  exception : " + e.getMessage());  
  35.         } finally {  
  36.             if (cl != null) {  
  37.                 cl.detach();  
  38.             }  
  39.         }  
  40.         return transformed;  
  41.     }  
  42.     private void doMethod(CtBehavior method) throws NotFoundException,  
  43.             CannotCompileException {  
  44.         // method.insertBefore("long stime = System.nanoTime();");  
  45.         // method.insertAfter("System.out.println(/"leave "+method.getName()+" and time:/"+(System.nanoTime()-stime));");  
  46.         method.instrument(new ExprEditor() {  
  47.             public void edit(MethodCall m) throws CannotCompileException {  
  48.                 m  
  49.                         .replace("{ long stime = System.nanoTime(); $_ = $proceed($$); System.out.println(/""  
  50.                                 + m.getClassName()+"."+m.getMethodName()  
  51.                                 + ":/"+(System.nanoTime()-stime));}");  
  52.             }  
  53.         });  
  54.     }  
  55. }  


上面两个类就是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));

 

3) 打包agent
对于agent的打包,有点讲究,
3.1)
jar的META-INF/MANIFEST.MF加入Premain-Class: xx, xx在此语境中就是我们的agent类,即org.toy.PerfMonAgent
3.2)
如果你的agent类引入别的包,需使用Boot-Class-Path: xx,xx在此语境中就是上面提到的jboss javassit 即 /home/pwlazy/.m2/repository/javassist/javassist/3.8.0 .GA/javassist-3.8.0.GA.jar

下面附上maven 的pom

[xhtml]  view plain copy
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3.   <modelVersion>4.0.0</modelVersion>  
  4.   <groupId>org.toy</groupId>  
  5.   <artifactId>toy-inst</artifactId>  
  6.   <packaging>jar</packaging>  
  7.   <version>1.0-SNAPSHOT</version>  
  8.   <name>toy-inst</name>  
  9.   <url>http://maven.apache.org</url>  
  10.   <dependencies>  
  11.      <dependency>  
  12.       <groupId>javassist</groupId>  
  13.       <artifactId>javassist</artifactId>  
  14.       <version>3.8.0.GA</version>  
  15.     </dependency>  
  16.     <dependency>  
  17.       <groupId>junit</groupId>  
  18.       <artifactId>junit</artifactId>  
  19.       <version>3.8.1</version>  
  20.       <scope>test</scope>  
  21.     </dependency>  
  22.   </dependencies>  
  23.    
  24.    <build>  
  25.     <plugins>  
  26.       <plugin>  
  27.         <groupId>org.apache.maven.plugins</groupId>  
  28.         <artifactId>maven-jar-plugin</artifactId>  
  29.         <version>2.2</version>  
  30.         <configuration>  
  31.           <archive>  
  32.             <manifestEntries>  
  33.               <Premain-Class>org.toy.PerfMonAgent</Premain-Class>  
  34.               <Boot-Class-Path>/home/pwlazy/.m2/repository/javassist/javassist/3.8.0.GA/javassist-3.8.0.GA.jar</Boot-Class-Path>  
  35.             
  36.             </manifestEntries>  
  37.           </archive>  
  38.         </configuration>  
  39.       </plugin>  
  40.        
  41.       <plugin>  
  42.        <artifactId>maven-compiler-plugin </artifactId >  
  43.               <configuration>  
  44.                   <source> 1.6 </source >  
  45.                   <target> 1.6 </target>  
  46.               </configuration>  
  47.      </plugin>  
  48.     </plugins>   
  49.       
  50.      
  51.   </build>  
  52.    
  53. </project>  


最终打成一个包toy-inst-1.0-SNAPSHOT.jar


4)打包应用
随便写个应用

[java]  view plain copy
  1. package org.toy;  
  2. public class App {  
  3.     public static void main(String[] args) {  
  4.         new App().test();  
  5.     }  
  6.     public void test() {  
  7.         System.out.println("Hello World!!");  
  8.     }  
  9. }  
   

最终打成一个包toy-1.0-SNAPSHOT.jar

5)执行命令

[python]  view plain copy
  1. java -javaagent:target/toy-inst-1.0-SNAPSHOT.jar -cp  /home/pwlazy/work/projects/toy/target/toy-1.0-SNAPSHOT.jar org.toy.App   


java选项中有-javaagent:xx,xx就是你的agent jar,java通过此选项加载agent,由agent来监控classpath下的应用。

最后的执行结果: 
[xhtml]  view plain copy
  1. PerfMonAgent.premain() was called.  
  2. Adding a PerfMonXformer instance to the JVM.  
  3. Transforming org/toy/App  
  4. Hello World!!  
  5. java.io.PrintStream.println:314216  
  6. org.toy.App.test:540082  
  7. Transforming java/lang/Shutdown  
  8. Transforming java/lang/Shutdown$Lock  
  9. java.lang.Shutdown.runHooks:29124  
  10. java.lang.Shutdown.sequence:132768  

 

我们由执行结果可以看出执行顺序以及通过改变org.toy.App的字节码加入监控代码确实生效了。

你也可以发现通过instrment实现agent是的监控代码和应用代码完全隔离了。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值