`

Java SE 6新特性:Instrumentation

阅读更多

(转自http://baike.baidu.com/view/1589420.html

 

Java SE 6新特性:Instrumentation,利用 Java 代码,即 java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。

  在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。

  在 Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能。在 Java SE 5 中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作。但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了 instrument 的应用。而 Java SE 6 的新特性改变了这种情况,通过 Java Tool API 中的 attach 方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。

  另外,对 native 的 Instrumentation 也是 Java SE 6 的一个崭新的功能,这使以前无法完成的功能 —— 对 native 接口的 instrumentation 可以在 Java SE 6 中,通过一个或者一系列的 prefix 添加而得以完成。


  最后,Java SE 6 里的 Instrumentation 也增加了动态添加 class path 的功能。所有这些新的功能,都使得 instrument 包的功能更加丰富,从而使 Java 语言本身更加强大。

 

 

Instrumentation 的基本功能和用法

“java.lang.instrument”包的具体实现,依赖于 JVMTI。JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的,为 JVM 相关的工具提供的本地编程接口集合。JVMTI 是从 Java SE 5 开始引入,整合和取代了以前使用的 Java Virtual Machine Profiler Interface (JVMPI) 和 the Java Virtual Machine Debug Interface (JVMDI),而在 Java SE 6 中,JVMPI 和 JVMDI 已经消失了。JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。事实上,java.lang.instrument 包的实现,也就是基于这种机制的:在 Instrumentation 的实现当中,存在一个 JVMTI 的代理程序,通过调用 JVMTI 当中 Java 类相关的函数来完成 Java 类的动态操作。除开 Instrumentation 功能外,JVMTI 还在虚拟机内存管理,线程控制,方法和变量操作等等方面提供了大量有价值的函数。

  Instrumentation 的最大作用,就是类定义动态改变和操作。在 Java SE 5 及其后续版本当中,开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 –javaagent 参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。

  在 Java SE 5 当中,开发者可以让 Instrumentation 代理在 main 函数运行前执行。简要说来就是如下几个步骤:

  编写 premain 函数

  编写一个 Java 类,包含如下两个方法当中的任何一个

  public static void premain(String agentArgs, Instrumentation inst); [1]

  public static void premain(String agentArgs); [2]

  其中,[1] 的优先级比 [2] 高,将会被优先执行([1] 和 [2] 同时存在时,[2] 被忽略)。

  在这个 premain 函数中,开发者可以进行对类的各种操作。

  agentArgs 是 premain 函数得到的程序参数,随同 “–javaagent”一起传入。与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。

  Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。

  jar 文件打包

  将这个 Java 类打包成一个 jar 文件,并在其中的 manifest 属性当中加入” Premain-Class”来指定步骤 1 当中编写的那个带有 premain 的 Java类。(可能还需要指定其他属性以开启更多功能)

   运行

  用如下方式运行带有 Instrumentation 的 Java 程序:

  java -javaagent:jar文件的位置[=传入premain的参数]

  对 Java 类文件的操作,可以理解为对一个 byte 数组的操作(将类文件的二进制字节流读入一个 byte 数组)。开发者可以在“ClassFileTransformer”的 transform 方法当中得到,操作并最终返回一个类的定义(一个 byte 数组)。这方面,Apache 的 BCEL 开源项目提供了强有力的支持,读者可以在参考文章“Java SE 5 特性 Instrumentation 实践”中看到一个 BCEL 和 Instrumentation 结合的例子。具体的字节码操作并非本文的重点,所以,本文中所举的例子,只是采用简单的类文件替换的方式来演示 Instrumentation 的使用。

  下面,我们通过简单的举例,来说明 Instrumentation 的基本使用方法。

  首先,我们有一个简单的类,TransClass, 可以通过一个静态方法返回一个整数 1。

  public class TransClass {

  public int getNumber() {

  return 1;

  } 

  }

  我们运行如下类,可以得到输出 ”1“。

  public class TestMainInJar {

  public static void main(String[] args) {

  System.out.println(new TransClass().getNumber());

  }

  }

  然后,我们将 TransClass 的 getNumber 方法改成如下:

  public int getNumber() {

  return 2;

  }

  再将这个返回 2 的 Java 文件编译成类文件,为了区别开原有的返回 1 的类,我们将返回 2 的这个类文件命名为 TransClass2.class.2。

  接下来,我们建立一个 Transformer 类:

    Java 代码:

 

      import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.lang.instrument.ClassFileTransformer;
  import java.lang.instrument.IllegalClassFormatException;
  class Transformer implements ClassFileTransformer {
  public static final String classNumberReturns2 = "TransClass.class.2";
  public static byte[] getBytesFromFile(String fileName) {
  try {
  // precondition
  File file = new File(fileName);
  InputStream is = new FileInputStream(file);
  long length = file.length();
  byte[] bytes = new byte[(int) length];
  // Read in the bytes
  int offset = 0;
  int numRead = 0;
  while (offset <bytes.length
  && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
  offset += numRead;
  }
  if (offset < bytes.length) {
  throw new IOException("Could not completely read file "+ file.getName());
  }
  is.close();
  return bytes;
  } catch (Exception e) {
  System.out.println("error occurs in _ClassTransformer!"+ e.getClass().getName());
  return null;
  }
  }
  public byte[] transform(ClassLoader l, String className, Class<?> c,
  ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {
  if (!className.equals("TransClass")) {
  return null;
  }
  return getBytesFromFile(classNumberReturns2);
  }
      }
 

 

  这个类实现了 ClassFileTransformer 接口。其中,getBytesFromFile 方法根据文件名读入二进制字符流,而 ClassFileTransformer 当中规定的 transform 方法则完成了类定义的替换转换。

  最后,我们建立一个 Premain 类,写入 Instrumentation 的代理方法 premain:

  Java 代码:

public class Premain {
  public static void premain(String agentArgs, Instrumentation inst)
  throws ClassNotFoundException, UnmodifiableClassException {
  inst.addTransformer(new Transformer());
  }
}

 

  可以看出,addTransformer 方法并没有指明要转换哪个类。转换发生在 premain 函数执行之后,main 函数执行之前,这时每装载一个类,transform 方法就会执行一次,看看是否需要转换,所以,在 transform(Transformer 类中)方法中,程序用 className.equals("TransClass") 来判断当前的类是否需要转换。

  代码完成后,我们将他们打包为 TestInstrument1.jar。返回 1 的那个 TransClass 的类文件保留在 jar 包中,而返回 2 的那个 TransClass.class.2 则放到 jar 的外面。在 manifest 里面加入如下属性来指定 premain 所在的类:

  Manifest-Version: 1.0

  Premain-Class: Premain

  在运行这个程序的时候,如果我们用普通方式运行这个 jar 中的???椠?? main 函数,可以得到输出“1”。如果用下列方式运行:

  java –javaagent:TestInstrument1.jar –cp TestInstrument1.jar TestMainInJar

  则会得到输出“2”。

  当然,程序运行的 main 函数不一定要放在 premain 所在的这个 jar 文件里面,这里只是为了例子程序打包的方便而放在一起的。

  除开用 addTransformer 的方式,Instrumentation 当中还有另外一个方法“redefineClasses”来实现 premain 当中指定的转换。用法类似,如下:

   Java 代码:

 

public class Premain {
  public static void premain(String agentArgs, Instrumentation inst)
  throws ClassNotFoundException, UnmodifiableClassException {
  ClassDefinition def = new ClassDefinition(TransClass.class, Transformer
  .getBytesFromFile(Transformer.classNumberReturns2));
  inst.redefineClasses(new ClassDefinition[] );
  System.out.println("success");
  }
}
 

  redefineClasses 的功能比较强大,可以批量转换很多类。

 

分享到:
评论

相关推荐

    javase6新特性

    Java SE 6,代号 Mustang,是Java发展过程中的一个重要版本,它在性能上有所提升,并引入了一系列新特性,尤其是在API库方面。其中,Instrumentation是Java SE 6的一个亮点,它允许开发者创建独立于应用程序的代理...

    JDK1.6新特性搜集整理

    5. **Instrumentation新功能**:JDK1.6扩展了Java代理(Java Agents)和Instrumentation API,允许开发者在运行时修改类文件,实现了对程序行为的深度监控和分析,这对于性能调试和代码优化非常有用。 6. **Java DB...

    JAVA_SE_6_新特征.rar

    利用 Java 代码,即 java.lang.instrument 做动态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者...

    Java Instrumentation笔记

    - **动态类路径添加**:在 Java 6 中,通过 `Instrumentation.appendToSystemClassLoaderSearch()` 方法,可以在运行时动态地添加新的类路径资源。 #### 八、总结 Java Instrumentation 提供了一种强大的机制,使...

    JDK6.0(Mustang)新特性

    在Java SE 6中,`java.lang.instrument`包被赋予了更强大的功能,包括启动后的Instrument、本地代码(native code)Instrument以及动态改变classpath等特性。这些变化进一步提升了Java语言的灵活性和动态性。 #####...

    基于java Instrumentation技术的数据采集探针验证.pdf

    在Java SE 5以后的版本中引入的Instrumentation接口允许开发者在不重新启动JVM的情况下动态地修改已经加载的类的行为。通过Instrumentation提供的接口,可以实现对类定义的修改,并通过添加agent的方式来改变应用的...

    (超赞)JAVA精华之--深入JAVA API

    #### 一、Java SE **1.1 深入 Java API** **1.1.1 Lang包** - **String类与 StringBuffer类** - `String` 类不可变,一旦创建后其内容无法更改;而 `StringBuffer` 类则允许在原有基础上修改字符串。`...

    Spring Framework 4

    - **适用范围**: 这个版本支持多种环境,包括但不限于 Java SE 6、Java SE 7 和 Java SE 8,同时也兼容 Java EE 6 和 Java EE 7 规范。 ##### 2. **核心概念与架构** - **依赖注入 (DI) 与控制反转 (IoC)**: ...

    j2se一句话学习笔记

    Java SE(J2SE)是Java平台标准版的简称,它是Java开发环境的基础,提供了丰富的类库和API供开发者使用。以下是对标题和描述中涉及的一些知识点的详细解释: 1. **Desktop类**:`java.awt.Desktop`类允许用户通过...

    Java 虚拟机面试题全面解析(干货)

    Java∨ ersion se6 支持脚本语言 引入JDBC40API; 引入 Java Compiler API; 可插拔注解; 增加对 Native PKi( Public Key Infrastructure)、 Java gss( Generic Security Service) Kerberos和 LDAP(Lightweight ...

    动力节点超完整详细的ssh和ssm讲义

    Spring框架是一个开源的轻量级Java SE/EE全栈解决方案,提供了对依赖注入、面向切面编程的支持,使得Java EE应用程序开发变得更加简单,功能更加强大。 - **核心容器**:包括Spring Core、Spring Beans、Spring ...

    Spring系列面试题129道(附答案解析)

    随着版本的更新,Spring引入了新特性和改进,例如对注解的更好支持、对异步处理的改进、对RESTful Web服务的支持等。 2、什么是SpringFramework? Spring是一个开源的Java/Java EE全功能栈应用程序框架,旨在简化...

    day-Spring IoC & DI.md

    Spring 是一个用于构建 Java SE/EE 应用的分层的、全栈式的轻量级开源框架,它以 IOC(控制反转)和 AOP(面向切面编程)作为其核心设计理念。Spring 提供了丰富的功能模块,包括展现层的 Spring MVC 和持久层的 ...

    spring官方文档

    Spring框架广泛应用于各种类型的企业级应用开发,无论是在传统的Java SE环境还是Java EE环境中,Spring都能发挥重要作用。具体应用场景包括但不限于: - **Web应用开发**:利用Spring MVC框架快速构建RESTful服务。...

Global site tag (gtag.js) - Google Analytics