返回介绍

7.5.1 Instrumentation 使用

发布于 2025-04-22 22:09:14 字数 7650 浏览 0 评论 0 收藏

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)写 ClassFileTransformer 类。

package org.toy;

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) {

     //修改 method 字节码

      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));");

 }

}

(2)编写 agent 类。

package org.toy;

import java.lang.instrument.Instrumentation;

import java.lang.instrument.ClassFileTransformer;

public class PerfMonAgent {

  static private 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);

 }

}

上面两个类就是 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 的打包,有点讲究。

JAR 的 META-INF/MANIFEST.MF 加入 Premain-Class: xx,xx 在此语境中就是我们的 agent 类,即 org.toy.PerfMonAgent。

如果你的 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 plaincopyprint?

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/

XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-

 v4_0_0.xsd">

 <modelVersion>4.0.0</modelVersion>

 <groupId>org.toy</groupId>

 <artifactId>toy-inst</artifactId>

 <packaging>jar</packaging>

 <version>1.0-SNAPSHOT</version>

 <name>toy-inst</name>

 <url>http://maven.apache.org</url>

 <dependencies>

  <dependency>

   <groupId>javassist</groupId>

   <artifactId>javassist</artifactId>

   <version>3.8.0.GA</version>

  </dependency>

  <dependency>

   <groupId>junit</groupId>

   <artifactId>junit</artifactId>

   <version>3.8.1</version>

   <scope>test</scope>

 </dependency>

</dependencies>

<build>

 <plugins>

  <plugin>

   <groupId>org.apache.maven.plugins</groupId>

   <artifactId>maven-jar-plugin</artifactId>

   <version>2.2</version>

   <configuration>

    <archive>

     <manifestEntries>

      <Premain-Class>org.toy.PerfMonAgent</Premain-Class>

      <Boot-Class-Path>/home/pwlazy/.m2/repository/javassist/javassist/3.8.0.GA/

      javassist-3.8.0.GA.jar</Boot-Class-Path>

     </manifestEntries>

    </archive>

   </configuration>

  </plugin>

  <plugin>

    <artifactId>maven-compiler-plugin </artifactId >

   <configuration>

     <source> 1.6 </source >

     <target> 1.6 </target>

   </configuration>

  </plugin>

 </plugins>

</build>

</project>

(4)打包应用。

package org.toy;

public class App {

  public static void main(String[] args) {

   new App().test();

 }

  public void test() {

   System.out.println("Hello World!!");

 }

}

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

最后的执行结果:

PerfMonAgent.premain() was called.

Adding a PerfMonXformer instance to the JVM.

Transforming org/toy/App

Hello World!!

java.io.PrintStream.println:314216

org.toy.App.test:540082

Transforming java/lang/Shutdown

Transforming java/lang/Shutdown$Lock

java.lang.Shutdown.runHooks:29124

java.lang.Shutdown.sequence:132768

由执行结果可以看出,执行顺序以及通过改变 org.toy.App 的字节码加入监控代码确实生效了。你也可以发现,通过 Instrment 实现 agent 使得监控代码和应用代码完全隔离了。

通过之前的两个小示例我们似乎已经有所体会,在 Spring 中的静态 AOP 直接使用了 AspectJ 提供的方法,而 AspectJ 又是在 Instrument 基础上进行的封装。就以上面的例子来看,至少在 AspectJ 中会有如下功能。

(1)读取 META-INF/aop.xml。

(2)将 aop.xml 中定义的增强器通过自定义的 ClassFileTransformer 织入对应的类中。

当然这都是 AspectJ 所做的事情,并不在我们讨论的范畴,Spring 是直接使用 AspectJ,也就是将动态代理的任务直接委托给了 AspectJ,那么,Spring 怎么嵌入 AspectJ 的呢?同样我们还是从配置文件入手。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。