一、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:



代码

1. import
2. import
3. import
4. import
5.    
6. public class FileClassLoader extends
7. public
8. byte[] data = loadClassData(name);    
9. return defineClass(name, data, 0, data.length);    
10.    }    
11.       
12. private byte[] loadClassData(String name) {    
13. null;    
14. byte[] data = null;    
15. try
16. new FileInputStream(new File("D://project//test//" + name + ".class"));    
17. new
18. int ch = 0;    
19. while ((ch = fis.read()) != -1) {    
20.          baos.write(ch);    
21.        }    
22.        data = baos.toByteArray();    
23. catch
24.        e.printStackTrace();    
25.      }    
26. return
27.    }    
28. }

MyApp.java:



代码

1. public class
2. public static void main(String[] args) throws
3. new
4. "MyApp");    
5.      Object obj = objClass.newInstance();    
6.      System.out.println(objClass.getName());    
7.      System.out.println(objClass.getClassLoader());    
8.      System.out.println(obj);    
9.    }    
10. }



编译并运行MyApp类,结果为:



代码

1. MyApp    
2. FileClassLoader@757aef
3. 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:



代码

1. public class
2. public static void
3. int i = 10000;    
4. "Hello Bytecode! Number = "
5.    }    
6. }




编译后的Test.class:



代码

1. 漱壕   1
2.        
3.    
4.    
5.    
6.    
7. <init> ()V Code LineNumberTable main ([Ljava/lang/String;)V    
8. SourceFile     Test.java    
9.    ! " 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 !    
10.          
11.         *                      >     ' < Y



使用javap -c Test > Test.bytecode生成的Test.bytecode:



代码

1. Compiled from "Test.java"
2. public class Test extends
3. public
4.    Code:    
5. 0:   aload_0    
6. 1:   invokespecial   #1; //Method java/lang/Object."<init>":()V 
7. 4:  return
8.    
9. public static void
10.    Code:    
11. 0:   sipush  10000
12. 3:   istore_1    
13. 4:   getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream; 
14. 7:  new   #3; //class java/lang/StringBuilder 
15. 10:   dup    
16. 11:   invokespecial   #4; //Method java/lang/StringBuilder."<init>":()V 
17. 14:   ldc   #5; //String Hello Bytecode! Number = 
18. 16:   invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
19. 19:   iload_1    
20. 20:   invokevirtual   #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 
21. 23:   invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
22. 26:   invokevirtual   #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
23. 29:  return
24.    
25. }

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是如何修改字节码的:



代码

1. class MethodWriter implements
2.    
3. private ByteVector code = new
4.    
5. public void visitIntInsn(final int opcode, final int
6. // Label currentBlock = this.currentBlock; 
7. if (currentBlock != null) {    
8. if
9. null, null);    
10. else if
11. // updates current and max stack sizes only for NEWARRAY 
12. // (stack size variation = 0 for BIPUSH or SIPUSH) 
13. int size = stackSize + 1;    
14. if
15.                      maxStackSize = size;    
16.                  }    
17.                  stackSize = size;    
18.              }    
19.          }    
20. // adds the instruction to the bytecode of the method 
21. if
22.              code.put12(opcode, operand);    
23. else { // BIPUSH or NEWARRAY 
24.              code.put11(opcode, operand);    
25.          }    
26.      }    
27.    
28. public void
29. final int
30. final
31. final
32. final
33.      {    
34. boolean
35.          Item i = cw.newMethodItem(owner, name, desc, itf);    
36. int
37. // Label currentBlock = this.currentBlock; 
38. if (currentBlock != null) {    
39. if
40. 0, cw, i);    
41. else
42. /* 
43.                   * computes the stack size variation. In order not to recompute 
44.                   * several times this variation for the same Item, we use the 
45.                   * intVal field of this item to store this variation, once it 
46.                   * has been computed. More precisely this intVal field stores 
47.                   * the sizes of the arguments and of the return value 
48.                   * corresponding to desc. 
49.                   */
50. if (argSize == 0) {    
51. // the above sizes have not been computed yet, 
52. // so we compute them... 
53.                      argSize = getArgumentsAndReturnSizes(desc);    
54. // ... and we save them in order 
55. // not to recompute them in the future 
56.                      i.intVal = argSize;    
57.                  }    
58. int
59. if
60. 2) + (argSize & 0x03) + 1;    
61. else
62. 2) + (argSize & 0x03);    
63.                  }    
64. // updates current and max stack sizes 
65. if
66.                      maxStackSize = size;    
67.                  }    
68.                  stackSize = size;    
69.              }    
70.          }    
71. // adds the instruction to the bytecode of the method 
72. if
73. if (argSize == 0) {    
74.                  argSize = getArgumentsAndReturnSizes(desc);    
75.                  i.intVal = argSize;    
76.              }    
77. 2, 0);    
78. else
79.              code.put12(opcode, i.index);    
80.          }    
81.      }    
82. }


通过注释我们可以大概理解visitIntInsn和visitMethodInsn方法的意思。

比如visitIntInsn先计算stack的size,然后根据opcode来判断是SIPUSH指令还是BIPUSH or NEWARRAY指令,并相应的调用字节码修改相关的方法。