java中的类加载器:bootstrap,ExtClassLoader,AppClassLoader
bootstrap 使用c++编写
sun.misc.Launcher.ExtClassLoader
sun.misc.Launcher.AppClassLoader

继承关系:

ClassLoader -> SecureClassLoader -> URLClassLoader -> sun.misc.Launcher$AppClassLoader

ClassLoader -> SecureClassLoader -> URLClassLoader -> sun.misc.Launcher$ExtClassLoader

下面一段话翻译自Javassist Tutorial:
    在java中,多个class loader可以共存,每一个class loader创建自己的命名空间。不同的类加载器可以加载同名的但内容不同的class文件。加载的2个类被认为是不同的。这个特性使我们可以在单个jvm内部运行不同的程序,即使这些程序包含同名的class。

  (注意:jvm不允许动态加载类。一旦一个class loader加载了某个类,该class loader不能在运行期内加载修改过的类。所以在jvm加载这个类后,你不能修改它的定义了。JPDA (Java Platform Debugger Architecture)提供了有限的重载一个类的能力。)

    如果同一个class文件被两个不同的class loader加载了,jvm会使用相同的名字和定义创建两个不同的class对象。这两个class被认为是不同的。因为这两个类是不同的,一个类的实例不能赋值给另一个类的变量。 两个类之间的转换操作会失败,并抛ClassCastException异常。
例如:下面的代码片段会抛出异常。
MyClassLoader myLoader = new MyClassLoader();
Class clazz = myLoader.loadClass("Box");
Object obj = clazz.newInstance();
Box b = (Box)obj;    // this always throws ClassCastException.


上面的话,用一幅图表示:

我们知道bootstrap是ExtClassLoader的父亲,ExtClassLoader是AppClassLoader的父亲,bootstrap负责rt.jar,ExtClassLoader负责ext目录中的jar,而AppClassLoader加载我们自己写的类。加载类的时候呢,使用的是双亲委派机制,先给父亲加载,在父亲的管辖范围内呢,父亲就会加载,反之则自己加载这个类。

但是,我们自己定义的类加载器,可以不使用双亲委派机制。

以下是一个自定义类加载器,from(https://yq.aliyun.com/articles/55616):

class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
            InputStream is = getClass().getResourceAsStream(fileName);
            if (is == null) {
                return super.loadClass(name);
            }
            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name, b, 0, b.length);
        }
        catch (Exception e) {
            throw new ClassNotFoundException(name);
        }
    }
}

线程内部有一个上下文类加载器:

public class Thread implements Runnable {
   /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader;
}

改变当前线程的contextClassLoader后,从当前线程派生出的子线程则使用修改后的contextClassLoader。

 

从类的Class对象可以获得加载该类的ClassLoader,从线程能获取该线程的contextClassLoader。

public class ClassLoader_Test {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(ClassLoader_Test.class.getClassLoader());
        System.out.println(Thread.currentThread().getContextClassLoader());
    }
}

平常,我们习惯用 Class.forName("xxx") 获取类的Class对象,其底层是调用ClassLoader.loadClass方法。

posted on 2018-01-25 10:51  偶尔发呆  阅读(206)  评论(0编辑  收藏  举报