深入了解Java ClassLoader、Bytecode 、ASM、cglib (I)
2012-03-12 16:25
148 查看
http://hi.baidu.com/maoshenmusic/blog/item/5e65dc2419baa6044c088d1a.html
一、Java ClassLoader 1,什么是ClassLoader 与 C 或 C++ 编写的程序不同,Java 程序并不是一个可执行文件,而是由许多独立的类文件组成,每一个文件对应于一个 Java 类。 此外,这些类文件并非立即全部都装入内存,而是根据程序需要装入内存。ClassLoader 是 JVM 中将类装入内存的那部分。 而且,Java ClassLoader 就是用 Java 语言编写的。这意味着创建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小细节。 2,一些重要的方法 A)loadClass ClassLoader.loadClass() 是ClassLoader的入口点。该方法的定义为:Class loadClass( String name, boolean resolve ); name:JVM 需要的类的名称,如 Foo 或 java.lang.Object。 resolve:参数告诉方法是否需要解析类。 B)defineClass defineClass方法是ClassLoader的主要诀窍。该方法接受由原始字节组成的数组并把它转换成Class对象。 C)findSystemClass findSystemClass方法从本地文件系统中寻找类文件,如果存在,就使用defineClass将原始字节转换成Class对象,以将该文件转换成类。 D)resolveClass 可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的loadClass时可以调用resolveClass,这取决于loadClass的resolve参数的值。 E)findLoadedClass findLoadedClass充当一个缓存:当请求loadClass装入类时,它调用该方法来查看ClassLoader是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。 3,Java2中ClassLoader的变动 1)loadClass的缺省实现 在Java2中loadClass的实现嵌入了大多数查找类的一般方法,并使您通过覆盖findClass方法来定制它,在适当的时候findClass会调用loadClass。 这种方式的好处是可能不一定要覆盖loadClass,只要覆盖findClass就行了,这减少了工作量。 2)新方法:findClass loadClass的缺省实现调用这个新方法。 3)新方法:getSystemClassLoader 如果覆盖findClass或loadClass,getSystemClassLoader让我们以实际ClassLoader对象来访问系统ClassLoader,而不是固定的从findSystemClass 调用它。 4)新方法:getParent 为了将类请求委托给父ClassLoader,这个新方法允许ClassLoader获取它的父ClassLoader。 4,定制ClassLoader 其实我们或多或少都使用过定制的ClassLoader,因为Applet查看器中就包含一个定制的ClassLoader。 它不在本地文件系统中寻找类,而是访问远程服务器上的 Web 站点,经过 HTTP 装入原始的字节码文件,并把它们转换成JVM 内的类。 Applet查看器中的ClassLoader还可以做其它事情:它们支持安全性以及使不同的Applet在不同的页面上运行而互不干扰。 我们将写一个自己的ClassLoader实现示例,它将实现如下步骤,这也是ClassLoader的工作原理: # 调用 findLoadedClass 来查看是否存在已装入的类。 # 如果没有,那么采用那种特殊的神奇方式来获取原始字节。 # 如果已有原始字节,调用defineClass将它们转换成Class对象。 # 如果没有原始字节,然后调用findSystemClass查看是否从本地文件系统获取类。 # 如果resolve参数是true,那么调用resolveClass解析Class对象。 # 如果还没有类,返回ClassNotFoundException。 # 否则,将类返回给调用程序。 话不多说,看看代码先: FileClassLoader.java: 代码 import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class FileClassLoader extends ClassLoader { public Class findClass(String name) { byte[] data = loadClassData(name); return defineClass(name, data, 0, data.length); } private byte[] loadClassData(String name) { FileInputStream fis = null; byte[] data = null; try { fis = new FileInputStream(new File("D:\\project\\test\\" + name + ".class")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int ch = 0; while ((ch = fis.read()) != -1) { baos.write(ch); } data = baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return data; } } MyApp.java: 代码 public class MyApp { public static void main(String[] args) throws Exception { FileClassLoader loader = new FileClassLoader(); Class objClass = loader.findClass("MyApp"); Object obj = objClass.newInstance(); System.out.println(objClass.getName()); System.out.println(objClass.getClassLoader()); System.out.println(obj); } } 编译并运行MyApp类,结果为: 代码 MyApp FileClassLoader@757aef MyApp@9cab16 二、Bytecode 1,什么是Bytecode C/C++编译器把源代码编译成汇编代码,Java编译器把Java源代码编译成字节码bytecode。 Java跨平台其实就是基于相同的bytecode规范做不同平台的虚拟机,我们的Java程序编译成bytecode后就可以在不同平台跑了。 .net框架有IL(intermediate language),汇编是C/C++程序的中间表达方式,而bytecode可以说是Java平台的中间语言。 了解Java字节码知识对debugging、performance tuning以及做一些高级语言扩展或框架很有帮助。 2,使用javap生成Bytecode JDK自带的javap.exe文件可以反汇编Bytecode,让我们看个例子: Test.java: 代码 public class Test { public static void main(String[] args) { int i = 10000; System.out.println("Hello Bytecode! Number = " + i); } } 编译后的Test.class: 代码 漱壕 1 + <init> ()V Code LineNumberTable main ([Ljava/lang/String;)V SourceFile Test.java ! " java/lang/StringBuilder Hello Bytecode! Number = # $ # % & ' ( ) * Test java/lang/Object java/lang/System out Ljava/io/PrintStream; append -(Ljava/lang/String;)Ljava/lang/StringBuilder; (I)Ljava/lang/StringBuilder; toString ()Ljava/lang/String; java/io/PrintStream println (Ljava/lang/String;)V ! * > ' < Y 使用javap -c Test > Test.bytecode生成的Test.bytecode: 代码 Compiled from "Test.java" public class Test extends java.lang.Object{ public Test(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: sipush 10000 3: istore_1 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 7: new #3; //class java/lang/StringBuilder 10: dup 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V 14: ldc #5; //String Hello Bytecode! Number = 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: iload_1 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 29: return } JVM就是一个基于stack的机器,每个thread拥有一个存储着一些frames的JVM stack,每次调用一个方法时生成一个frame。 一个frame包括一个local variables数组(本地变量表),一个Operand LIFO stack和运行时常量池的一个引用。 简单分析一下生成的字节码指令: aload和iload指令的“a”前缀和“i”分别表示对象引用和int类型,其他还有“b”表示byte,“c”表示char,“d”表示double等等 我们这里的aload_0表示将把local variable table中index 0的值push到Operand stack,iload_1类似 invokespecial表示初始化对象,return表示返回 sipush表示把10000这个int值push到Operand stack getstatic表示取静态域 invokevirtual表示调用一些实例方法 这些指令又称为opcode,Java一直以来只有约202個Opcode,具体请参考Java Bytecode规范。 我们看到Test.class文件不全是二进制的指令,有些是我们可以识别的字符,这是因为有些包名、类名和常量字符串没有编译成二进制Bytecode指令。 3,体验字节码增强的魔力 我们J2EE常用的Hibernate、Spring都用到了动态字节码修改来改变类的行为。 让我们通过看看ASM的org.objectweb.asm.MethodWriter类的部分方法来理解ASM是如何修改字节码的: 代码 class MethodWriter implements MethodVisitor { private ByteVector code = new ByteVector(); public void visitIntInsn(final int opcode, final int operand) { // Label currentBlock = this.currentBlock; if (currentBlock != null) { if (compute == FRAMES) { currentBlock.frame.execute(opcode, operand, null, null); } else if (opcode != Opcodes.NEWARRAY) { // updates current and max stack sizes only for NEWARRAY // (stack size variation = 0 for BIPUSH or SIPUSH) int size = stackSize + 1; if (size > maxStackSize) { maxStackSize = size; } stackSize = size; } } // adds the instruction to the bytecode of the method if (opcode == Opcodes.SIPUSH) { code.put12(opcode, operand); } else { // BIPUSH or NEWARRAY code.put11(opcode, operand); } } public void visitMethodInsn( final int opcode, final String owner, final String name, final String desc) { boolean itf = opcode == Opcodes.INVOKEINTERFACE; Item i = cw.newMethodItem(owner, name, desc, itf); int argSize = i.intVal; // Label currentBlock = this.currentBlock; if (currentBlock != null) { if (compute == FRAMES) { currentBlock.frame.execute(opcode, 0, cw, i); } else { /* * computes the stack size variation. In order not to recompute * several times this variation for the same Item, we use the * intVal field of this item to store this variation, once it * has been computed. More precisely this intVal field stores * the sizes of the arguments and of the return value * corresponding to desc. */ if (argSize == 0) { // the above sizes have not been computed yet, // so we compute them... argSize = getArgumentsAndReturnSizes(desc); // ... and we save them in order // not to recompute them in the future i.intVal = argSize; } int size; if (opcode == Opcodes.INVOKESTATIC) { size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; } else { size = stackSize - (argSize >> 2) + (argSize & 0x03); } // updates current and max stack sizes if (size > maxStackSize) { maxStackSize = size; } stackSize = size; } } // adds the instruction to the bytecode of the method if (itf) { if (argSize == 0) { argSize = getArgumentsAndReturnSizes(desc); i.intVal = argSize; } code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0); } else { code.put12(opcode, i.index); } } } 通过注释我们可以大概理解visitIntInsn和visitMethodInsn方法的意思。 比如visitIntInsn先计算stack的size,然后根据opcode来判断是SIPUSH指令还是BIPUSH or NEWARRAY指令,并相应的调用字节码修改相关的方法。 |
相关文章推荐
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib (I)
- 【转】 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- [转载] 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 主题: 深入了解Java ClassLoader、Bytecode 、ASM、cglib
- 深入了解Java ClassLoader、Bytecode 、ASM、cglib(II)
- 深入了解Java ClassLoader,Bytecode,ASM,Cglib
- ASM + JASMIN combination for java class file editing in bytecode level
- 深入了解Java ClassLoader(转帖)
- 使用ASM反编译Java bytecode得到其汇编码全部类
- Instrumenting Java Bytecode with ASM
- 深入分析Java ClassLoader原理(面试问题:你了解java类加载器么)
- 深入了解java之虚拟机内存
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- Java动态代理机制原理详解(JDK 和CGLIB,Javassist,ASM)
- 了解 JAVA classloader