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>
Z终打成一个包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. }
Z终打成一个包toy-1.0-SNAPSHOT.jar
5)执行命令
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下的应用。
Z后的执行结果:
[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是的监控代码和应用代码完全隔离了。