Inside Class Loaders深入理解类加载器

原文:http://www.onjava.com/pub/a/onjava/2003/11/12/classloader.html,本文有删减。
First we need to explain some definitions:
首先我们来解释一些名字的定义:
CL:               Class loader(类加载器).             
Initial CL: The CL that initiated the loading of the class(发起加载某个类请求的类加载器,因为是代理加载,因此,发起类加载请求的和实际加载类的可能不是同一个类加载器).
Effective CL: The CL that actually loaded the class(实际加载类的类加载器).
Class type: The fully qualified class name (package plus class name)(类的完全限定名:包名+类名).
Class:         A combination of the class type and effective class loader(类的完全限定名+实际的类加载器).
java.lang.Class: A class in the JDK that represents a class (name, fields, methods, etc.).(jdk中的Class)
Symbolic Link:  A class type used within the source code, such as superclasses, extended interfaces, variables, parameters, return values, instanceofs, and upcasts(源文件中类的完全限定名,比如:父类,接口,变量,参数,返回值,instanceof等等).
                 
Class loaders and their usage follow a few simple rules:
类加载的使用遵循以下几个规则:
Class loaders are hierarchically organized, where each one has a parent class loader, except the bootstrap class loader (the root).
Class loaders should (practically: must) delegate the loading of a class to the parent, but a custom class loader can define for itself when it should do so.
A class is defined by its class type and the effective class loader.
A class is only loaded once and then cached in the class loader to ensure that the byte code cannot change.
Any symbolic links are loaded by the effective class loader (or one of its ancestors), if this is not already done. The JVM can defer this resolution until the class is actually used.
An upcast of an instance to another class fails when the class of the instance and the class of the symbolic link do not match (meaning their class loaders do not match).
Now I want to put on some meat to these bare-bone rules to provide better understanding.
类加载器是一个有等级的结构,每一个类加载器都有一个父类加载器,除了bootstrap类加载器(也叫根加载器)
类加载器“应该”(实际中是“必须”)把加载一个类的请求代理给他的父类加载器,但是,自定义的类加载器也可以自由实现如果它需要那么做的时候。
一个类是由它的完全限定名和实际的类加载器来定义的。
一个类只会被一个类加载器加载一次,然后会被类加载器缓存,这样就保证了类的字节码不会被更改。
任何的符号引用都是被实际类加载器(或者父类加载器)载入的(这个需要说明下, 比如说,我们自定义的类加载器载入的一个类,如何才能和系统类加载器载入的类协同工作呢?正常来说,自定义的类加载,也是采用代理的方式来载入类,当载入一个类的时候,首先要载入它的父类,只要把父类让系统类加载器来载入就可以了。我们可以事先定义好一组接口或者基类并放入CLASSPATH中,那么父类的引用就是由系统类加载器载入的,然后在执行的时候,用自定义的类加载器动态的载入子类,运行时根据多态会去调用实际的子类的方法。看后面的例子)。jvm可以延迟到真正使用这个类再进行解析。
把一个类实例转型成另一个类型会失败,如果类实例和符号引用不匹配(也就说明他们的类加载器不匹配)


Class Loader Organization and Delegation


Before we start, let's look at a typical class loader hierarchy, as illustrated by Figure 1:
           BootStrap(BS)
                    |
           SystemCL(CP)
                    |
    ------------------------
    |                |            |
  CL:A1     CL:A2    CL:A3
                                   |
                          ---------------
                       CL:B1      CL:B2
Figure 1. Class loader hierarchy example


As shown in Figure 1, the bootstrap class loader (BS) loads the classes from the JVM, as well as extensions to the JDK. 
The system class loader (CP) loads all of the classes provided by the CLASSPATH environment variable or passed using the -classpath argument to the java command.Finally we have several additional class loaders, where A1-3 are children of the CP, and B1-2 are children of A3. Every class loader (except BS) has a parent class loader, even if no parent is provided explicitly; in the latter case, the CP is automatically set as the parent.


That alone does not mean much but has a big impact on class-loading delegation. 
The Javadoc of java.lang.ClassLoader specifies that any class loader must first delegate the loading of a class to the parent, and only if this fails does it try to load the class itself. Actually, the class loader does not care about which one gets the bytes of the class, but rather which one calls defineClass(). In this final method, an instance of class java.lang.Class is created and cached in the class loader so that the byte code of a class cannot change on a following request to load the class. This method also checks that the given class name matches the class name in the byte code. Because this method is final, no custom class loader can change this behavior.
java.lang.ClassLoader文档指明了,任何的类加载器必须首先把类加载请求代理给它的父类加载器,只有当父类加载器加载失败的时候,类加载器器才需要自己进行加载。实际上,类加载器并不关心类的字节数组是由哪一个类加载获取的,它关心的是哪一个类加载器调用了defineClass()方法。在defineClass()这个final方法中,java.lang.Class的实例被创建出来,并且被类加载器缓存,所以在后续的请求中,这个类的字节码就不能被改变了。这个方法还会检查给定的类名和字节码里面的类名是否匹配,因为这个方法是final的,因此没有任何的子类加载器能改变它的行为。
As previously mentioned, a class loader must delegate the loading of a class (although a developer can override loadClass() and change this behavior). On one hand, if loading of system classes is not delegated, an application could provide malicious code for JDK classes and introduce a ton of problems. On the other hand, all classes at least extend java.lang.Object, and this class must be resolved, too. Thus the custom class loader has to load this class by itself, otherwise the load fails with a linkage error. These two facts imply that a custom class loader has to delegate class loading. In JDK 1.4, two of the three versions of defineClass() throw a SecurityException if the given class name starts with "java", while the third version is deprecated due to these security concerns.
就像在前面提到的那样,一个类加载器必须要代理类的加载(尽管开发者可以通过覆盖loadClass()来改变这种行为)。
一方面,如果载入系统类不被代理,应用程序就可以给jdk里面的类提供有害的代码,这会导致一大堆的问题。
另一方面,所有的类至少都继承了java.lang.Object,并且这个类必须得resolve。如果不代理,自定义的类加载器就需要自己来载入这个类,否则就会出现链接错误。这两个方面暗示了,自定义的类加载器需要代理类加载。
在jdk1.4里面,3个版本的defineClass()里面的2个都会抛出SecurityException,如果给定的类名以java开头,第3个方法已经被废弃掉了,由于各种安全原因。


I want to stress the fact here that there is a difference between the class loader that starts the process of loading the class and the one that actually loads (defines) the class. Assuming that in our example no class loader delegates the loading of a class to one of its children, any class is either loaded by the Initial CL or by one of its ancestors. Let us assume that a class A contains a symbolic link to class B that in turn contains a symbolic link to class C. The class loader of C can never be a child of the class loader of B or of A. Of course, one should never say "never," and yes, it is possible to break this rule, but like multiple inheritance in C++, this is "black belt" programming.
我想要强调的是,开始类加载请求的类加载器和实际加载类的类加载器有时候是不一样的。


A more prominent exception of the JDK delegation model of "delegating first" is the class loader for a web container described in the servlet specification. This one tries to load a class first by itself before it delegates to the parent. Nevertheless, some classes, such as java.*, javax.*, org.xml.sax.* and others, are delegated first to the parent. For more information, please check out the Tomcat 5.0 documentation.


Class Linking
类的链接
After a class is defined with defineClass(), it must be linked in order to be usable by the final resolveClass() method. Between this method call and the first usage of a symbolic link, the class type is loaded by the class loader of the containing class as Initial CL. If any linked class (type) cannot be loaded, the method will throw a linkage error (java.lang.NoClassDefFoundError). Keep in mind that the resolution of symbolic links is up to the JVM and can be done anywhere between the loading of the containing class (eager resolution or C-style) and the first actual usage of the symbolic link (lazy resolution). It can happen that a symbolic link is in a class and if it is never used, the linked class will never be loaded such as in this example with JDK 1.4.2 on Windows 2000:
当一个class被defineClass()定义以后,它还得通过调用resoveClass()进行链接以后才能被使用。
在resoveClass()方法中,class type是由初始的类加载器载入的。如果链接过程中,任何一个class type不能被载入,这个方法都会抛出NoClassDefFoundError。记住,解析符号引用取决于jvm,它可以在载入class的时候就进行解析,也可以在第一次使用这个引用的时候再解析。
如果类里面有一个符号引用,但是,这个引用一直就没用被使用,那么被链接的类就永远都不会被加载进来,比如:windows 2000上的jdk1.4.2。
public class M {
    // In JDK 1.4.2 on W2K this class can be used
    // fine even if class O is not available.
public O mMyInstanceOfO;
}
whereas this class will fail with a linkage error if the class O cannot be loaded:


public class M {
    // In JDK 1.4.2 and W2K the creation of an
    // instance of M will FAIL with
    // a NoClassDefFoundError if class O is not
    // available
public O mMyInstanceOfO = new O();
}
and to make matters a little bit more complicated, it only fails when an instance is created:


    // Fine because in JDK 1.4.2 on W2K class
    // linking is done lazy
    Class lClassM = Class.forName("M");
    // Fails with NoClassDefFoundError
    Object lObject = lClassM.newInstance();
For more information, please read Chapter 12: "Execution" in the Java Language Specification.


Class Definition
类的定义
To a beginner, a class is identified solely by the class type. As soon as you start to deal with class loaders, this is no longer the case. 
Provided that class type M is not available to CP, A1 and A2 could load the same class type M with different byte code. Even when the byte code would be the same from a Java point of view, these classes are different, no matter if the byte code is the same or not. To avoid ambiguities, a class is identified by its class type as well as the Effective CL, and I will use the notation <Class Name>-<Class Loader>. So for this case, we have classes M-A1 and M-A2. Imagine we also have another class, Test-A1, with a method upcastM() that looks like this:
对于初学者来说,类是由它的完全限定名来唯一标示的。但是,当你知道了类加载器以后,就不是这样了。
比如,类M对System类加载器不可见,类加载器A1和A2都可以载入M,不管这两个M的字节码是不是一样的,在jvm看来,这两个M都是不一样的。为了避免歧义,一个类是由它的完全限定名和实际的类加载器来唯一标识,我会使用<类名>-<类加载器>这种形式。
对于这个例子来说,我们有M-A1和M-A2两个类。假设,我们有另一个类:Test-A1,还有一个方法upcastM():
public void upcastM(Object pInstance)
        throws Exception {
       M lM = (M) pInstance;
}
Because the class Test is loaded by A1, its symbolic link M is also loaded by A1. So we are going to upcast a given object to M-A1. 
When this method is called with an instance of the class M-A1 as an argument, it will return successfully, but if it is called with an instance of M-A2, it will throw a ClassCastException because it is not the same class, according to the JVM. Even with reflection this rule is enforced, because both java.lang.Class.newInstance() and java.lang.reflect.Constructor.newInstance() return an instance of class java.lang.Object-BS. 
Unless only reflection is used during the lifetime of this object, the instance has to be upcast at some point. In the case of only using reflection to avoid conflicts, any arguments of a method still be subject to an upcast to the class of the method signature and therefore the classes must match, otherwise you get a java.lang.IllegalArgumentException due to the ClassCastException.
因为Test是由A1加载的,它的符号引用M也是由A1加载的,所以我们就可以把Test转型成M-A1.当这个方法被调用的时候,如果M-A1作为参数,可以成功,如果M-A2作为参数,就会抛出ClassCastException,因为在jvm看来,他们不是同样的类。这个规则对反射同样适用,无论是java.lang.Class.newInstance()还是java.lang.reflect.Constructor.newInstance(),都会返回java.lang.Object-BS的实例。这个实例总得转型成某一个类型,除非只使用反射来调用它的方法。就算只使用反射来避免冲突,它里面的方法的参数仍面临着转型的问题。


Test
测试
The sample code(http://www.onjava.com/onjava/2003/11/12/examples/classloader.zip) may help the reader to better understand the concepts described above and, later, to do their own investigations. In order to run the sample code, just extract it in the directory of your choice and execute the ant build script in the classloader.part1.basics directory.


It has three directories: main, version_a, and version_b. The main directory contains the startup class Main.java as well as the custom class loader that will load classes from a given directory. The other two directories both contain one version of M.java and Test.java. 
The class Main will first create two custom class loaders each loading classes, after delegating to the parent class loader, 
from either the version_a or version_b directories. Then it will load the class M by each of these two class loaders and create an instance through reflection:


// Create two class loaders: one for each dir.
ClassLoader lClassLoader_A = new MyClassLoader("./build/classes/version_a" );
ClassLoader lClassLoader_B = new MyClassLoader("./build/classes/version_b" );
// Load Class M from first CL and create instance
Object lInstance_M_A = createInstance( lClassLoader_A, "M" );
// Load Class M from second CL and create instance
Object lInstance_M_B = createInstance( lClassLoader_B, "M" );
In order to test an upcast, I need a class where the Effective CL is one of the custom class loaders. 
I then use reflection in order to invoke a method on them because I cannot upcast them because Main is loaded by the CP:


// Check the upcast of a instance of M-A1 to class M-A1. This test must succeed because the CLs match.
try {
    checkUpcast(lClassLoader_A, lInstance_M_A );
    System.err.println("OK: Upcast of instance of M-A1"+ " succeeded to a class of M-A1" );
} catch (ClassCastException cce) {
    System.err.println("ERROR: Upcast of instance of M-A1"+ " failed to a class of M-A1" );
}
// Check the upcast of a instance of M-A2 to class M-A1. This test must fail because the CLs does not match.
try {
    checkUpcast(lClassLoader_A, lInstance_M_B );
    System.err.println("ERROR: upcast of instance of M-A2"+ " succeeded to a class of M-A1" );
} catch (ClassCastException cce) {
    System.err.println("OK: upcast of instance of M-A2 failed"+ " to a class of M-A1" );
}
The checkUpcast() loads the class Test through reflection and calls the Test.checkUpcast() method, which makes a simple upcast:


private static void checkUpcast(ClassLoader pTestCL, Object pInstance )throws Exception {
    try {
        Object lTestInstance = createInstance( pTestCL, "Test" );
        Method lCheckUpcastMethod =lTestInstance.getClass().getMethod("checkUpcast",new Class[] { Object.class } );
        lCheckUpcastMethod.invoke(lTestInstance,new Object[] { pInstance } );
    } catch( InvocationTargetException ite ) {
        throw (ClassCastException) ite.getCause();
    }
}


private static Object createInstance( ClassLoader pClassLoader, String pClassName ) throws Exception {
        Class lClass = pClassLoader.loadClass( pClassName );
        return lClass.newInstance();
}


Test:
public void checkUpcast( Object pTestInstance ) throws ClassCastException {
        M lTest = (M) pTestInstance;
    }
符号引用M是由初始类加载器载入的,也就是载入Test的类加载器,也就是MyClassLoader。


Afterwards, there are some tests that do the same thing, but check the upcast restriction against reflection to ensure that 
reflection cannot compromise the rules posted at the beginning of the article. The last test checks the linking of symbolic links.
On Windows 2000 and JDK 1.4.2, it will also show the lazy loading of classes because the loading of the class succeeds, 
whereas the creation of the instance eventually fails:


// Load a class N that has a symbolic link to class O that was removed so that the class resolving must fail
try {
    // Upcast ClassLoader to our version in order to access the normally protected loadClass() method with the resolve flag. 
    // Even the resolve flag is set to true the missing symbolic link is only detected in W2K and JDK 1.4.2 when the instance is created.
    Class lClassN = ( (MyClassLoader) lClassLoader_A).loadClass( "N", true );
    // Finally when the instance is created any used symbolic link must be resolved and the creation must fail
    lClassN.newInstance();
    System.err.println("ERROR: Linkage error not thrown even" + "class O is not available for"+ " class N" );
} catch( NoClassDefFoundError ncdfe ) {
    System.err.println("OK: Linkage error because class O"+ " could not be found for class N" );
}
Please note that in the directory version_a there is a class named O.java, because in order to compile the class N.java, this class is needed. 
However, the ant build script will remove the compiled class O.class before the test is started.


再来个例子:
目录结构:
sample2
       src
            main/Main.java
            main/MyLoader.java
            test/Parent.java
       test1
            test/Child.java
            test/Other.java
       test2
            test/Child.java
            test/Other.java
            test/Parent.java
Main.java:
package main;
import test.*;
public class Main{
public static void main(String args[])throws Exception{
    MyLoader loader = new MyLoader("../test1/");
    Class<?> clazz = loader.loadClass("test.Child");
Object obj = clazz.newInstance();
try{
test.Child child = (test.Child)obj;
child.print();
/*
这里会抛异常,符号引用test.Child是Appclassloader载入的,现在在classpath中找不到test.Child类
*/
}catch(Throwable e){
e.printStackTrace();
}
loader = new MyLoader("../test2/");
clazz = loader.loadClass("test.Child");
    obj = clazz.newInstance();
try{
test.Parent child = (test.Parent)obj;
child.print();
/*
符号引用test.Parent是由Appclassloader载入。
test.Child是由MyLoader载入,载入他的父类test.Parent的时候,根据代理,会由Appclassloader载入,刚好匹配上!
因此,能正常输出,根据多态,输出:child
*/
}catch(Throwable e){
e.printStackTrace();
}
}
}
切换到sample/src所在的目录,执行:
C:\Users\jiashuai.xujs\Desktop\classloader\sample2\src>javac -cp .;%classpth%; ../test1/test/*.java
C:\Users\jiashuai.xujs\Desktop\classloader\sample2\src>javac -cp .;%classpth%; ../test2/test/*.java
C:\Users\jiashuai.xujs\Desktop\classloader\sample2\src>javac -cp .;%classpth%;../test1 main/*.java
把所有的源码先编译一下,然后执行:
C:\Users\jiashuai.xujs\Desktop\classloader\sample2\src>java -cp .;%classpath%; main.Main
输出:
Child loaded by:main.MyLoader
Other loaded by:main.MyLoader
java.lang.NoClassDefFoundError: test/Child
        at main.Main.main(Main.java:10)
Caused by: java.lang.ClassNotFoundException: test.Child
        at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
        ... 1 more
Child loaded by:main.MyLoader
Other loaded by:main.MyLoader
child


本文提到的源码下载地址:http://download.csdn.net/detail/goldenfish1919/6293075


参考:
http://www.iteye.com/topic/136427(热加载的例子)
http://www.blogjava.net/lhulcn618/archive/2006/05/25/48230.html(很详细)

你可能感兴趣的:(java,ClassLoader)