`
huoyanxueren
  • 浏览: 38389 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Instrument小窥

 
阅读更多

java.lang.instrument是java 5开始引入的,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义.

Java5的特性:运行前利用命令行参数或者系统参数来设置代理类,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并根据设置完全特定功能.

例子1:

package instrumentexample;

public class MainClass {

	public static void main(String[] args) throws InterruptedException {
		System.out.println("MainClass invoke main function");
	}

}

 

package instrumentexample;

import java.io.IOException;
import java.lang.instrument.Instrumentation;

public class Premain {

	public static void premain(String agentArgs, Instrumentation inst)
			throws IOException {
		Class[] classes = inst.getAllLoadedClasses();
		for (Class classstr : classes) {
			System.out.println("Class:" + classstr.getName() + ";ClassLoader:"
					+ classstr.getClassLoader());
		}

	}

}

 

javac instrumentexample/Premain.java 

 jar -cvf agent.jar instrumentexample/Premain.class

 打包出一个agent.jar,并修改MANIFEST.MF 文件,添加 Premain-Class属性

 

Manifest-Version: 1.0
Premain-Class: instrumentexample.Premain
Created-By: 1.6.0_20 (Sun Microsystems Inc.)

  执行:java -javaagent:agent.jar instrumentexample/MainClass

 打印结果可看到:Class:instrumentexample.Premain;ClassLoader:sun.misc.Launcher$AppClassLoader@df6ccd

却看不到对应的MainClass被classload加载. 这个说明了在Premain被加载并执行后,MainClass还未被AppClassLoader加载入JVM. 而对应其他的被BootstrapClassLoader加载的类都已经被完全被加载了.莫忘记了,连同AppClassLoader这个类是Java实现,也是需要被ClassLoader加载的,而这个加载器正是BootstrapClassLoader.

 

instrument如何实现改变方法的执行呢?通过字节码修改方式.

例子2:

package instrumentexample;

public class Person {
	public void sayName() {
		System.out.println("I am Justin");
	}
}

package instrumentexample;

public class MainClass {

	public static void main(String[] args) throws InterruptedException {
		Person person = new Person();
		person.sayName();
	}

}

 

   想把Person的名称改成LiLei呢?instument有一个ClassFileTransformer接口,接口中只有一个唯一的方法.

 

   byte[]
    transform(  ClassLoader         loader,
                String              className,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)
        throws IllegalClassFormatException;

加了agent后,每个被加载的class都会被检查一遍. classfileBuffer是类文件的字节码被当作字节码数组传递进来,返回值被改变后的class字节码,如果返回null,则字节码不被修改.在该方法中,字节码的任何改变都会在ClassLoader中重新生效.我们可以通过字节码增强的工具如ASM,Cglin,BCEL等工具进行字节码增强,改变原来class,执行新功能.当然目前的只针对被 AppClassLoader加载的类,如果更改BootstrapClassLoader,ExtClassLoader加载的类,则需求其他的修改,后面会举例.

       

  下面用最简单的,重新加载一个class文件的方式实现字节码修改.修改后的新Person

 

public class Person {
	public void sayName() {
		System.out.println("I am LiLei");
	}
}

 

 编译并命名成Person.class.new.

 实现ClassFileTransformer接口

 

package instrumentexample;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class MethodTransformer implements ClassFileTransformer {

	public static final String newClassFilePath = "Person.class.new";

	// 从新class文件中读取字节码
	public static byte[] readBytesFromFile(String fileName) {
		try {
			File file = new File(fileName);
			InputStream is = new FileInputStream(file);
			byte[] bytes = new byte[(int) file.length()];
			is.read(bytes);
			is.close();
			return bytes;
		} catch (Exception e) {
			return null;
		}
	}

	// 实现接口,class字节码修改发生的地方
	public byte[] transform(ClassLoader l, String className, Class<?> c,
			ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {
		// 如果不是要修改的类,直接返回null
		if (!className.equals("instrumentexample/Person")) {
			return null;
		}
		return readBytesFromFile(newClassFilePath);
	}
}

再在Agent里面添加该MethodTransformer

public class PremainModifyClass {

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

}

 

 

PremainModifyClass和MethodTransformer打包成agent.jar,执行跟例子1一样.

 

 

java6引入的新特性:JVM启动后动态 instrument、本地代码(native code)instrument,以及动态改变 classpath.


JVM启动后动态 instrument:在虚拟机初始化完成后(绝大部分的类库都已经被加载完毕),通过Java Tool API 中的 attach 方式,在运行过程中动态的实现instrumentation.

 

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

premain()类似.

 

 

例子3:

 

public class Agentmain {

	public static void agentmain(String agentArgs, Instrumentation inst) {
		Class[] classes = inst.getAllLoadedClasses();
		for (Class classstr : classes) {
			System.out.println("Class:" + classstr.getName() + ";ClassLoader:"
					+ classstr.getClassLoader());
		}
	}
}

 

public class LoadAgentMain {

	public static void main(String[] args) throws AttachNotSupportedException,
			IOException, AgentLoadException, AgentInitializationException {
		// args[0]为JVM实例的pid,args[1]为agent jar的路径
		VirtualMachine vm = VirtualMachine.attach(args[0]);
		vm.loadAgent(args[1]);
	}
}

   修改例子1:

public class MainClass {

	public static void main(String[] args) throws InterruptedException {
		Person person = new Person();
		while (0 < 1) {

		}
	}
}

   jps命令行得到MainClass的pid, 打包AgentMain为agent.jar,修改Manifest文件

 Agent-Class: instrumentexample.Agentmain

java -javaagent:agent.jar MainClass pid path;

 

从打印出来的结果可以看到,MainClass和Person类都已经被AppClassLoader加载完成了. 这个就是premain()方式和agentmain()方式的区别.一个是在JVM实例启动前执行instrumentation,一个是在启动后执行instrumentation.

如果在agentmain()里面改变字节码,就实现了运行时的instrumentation.

 

运行时改变 BootClassPath/SystemClassPath

上面我们改变的都是被AppClasssLoader加载的class,如果要改变BootstrapClassLoader加载的class呢?

可以设置一个虚拟机运行时的 boot class 加载路径(-Xbootclasspath)和 system class(-cp)加载路径.当然,我们在运行之后无法替换它。然而,我们也许有时候要需要把某些 jar 加载到 bootclasspath 之中,而我们无法应用上述两个方法。在 Java SE 6 之中,可以实现.premain/agantmain 函数里面通过 appendToBootstrapClassLoaderSearch或 appendToSystemClassLoaderSearch 来动态添加ClassLoader的加载路径.( 虚拟机的独特 ClassLoader 的工作方式,它会记载解析结果。比如,我们曾经要求读入某个类 someclass,但是失败了,ClassLoader 会记得这一点。即使我们在后面动态地加入了某一个 jar,含有这个类,ClassLoader 依然会认为我们无法解析这个类,与上次出错的相同的错误会被报告。 )

 

文献引用:

Java SE 6 新特性: Instrumentation 新功能  http://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics