Java类加载器

java.lang.Class.forName(…)java.lang.ClassNotFoundExcetpion是我们经常遇到的, 藏在他们身后的,是Java的类加载器。



JVM预定义的三种类加载器

JVM预定义的三种类加载器有:启动(Bootstrap)类加载器、标准扩展(Extension)类加载器和系统(System)类加载器,当一个JVM启动的时候,Java缺省开始使用这些类装入器。
除了以上三种类加载器,还有一种比较特殊的类型就是线程上下文类加载器,这个暂不介绍。

启动(Bootstrap)类加载器

启动类加载器是通过java.lang.ClassLoader中的private native final Class findLoadedClass0(String name);这一本地方法实现的类装入器,它负责将<Java_Runtime_Home>/lib下面的类库加载到内存中。

标准扩展(Extension)类加载器

扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现,它负责将<Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。

系统(System)类加载器

系统类加载器是由Sun的AppClassLoader(sun.misc.Launcher$AppClassLoader)实现,它负责将系统类路径CLASSPATH中指定的类库加载到内存中。

上面三种类型的类加载器中,启动(Bootstrap)类加载器是通过本地方法实现的,无法直接获取到启动类加载器的引用;而标准扩展(Extension)类加载器和系统(System)类加载器均为sun.misc.Launcher的内部类,分别对应ExtClassLoaderAppClassLoader,这两个内部类均继承自URLClassLoader,其继承关系为:

java.net.URLClassLoader -> java.security.SecureClassLoader -> java.lang.ClassLoader

所以ExtClassLoaderAppClassLoader这两个内部类间接继承自ClassLoader


双亲委派机制

JVM在加载类时默认采用的是双亲委派机制,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给双亲加载器,依次递归,如果双亲加载器可以完成类加载任务,就成功返回;只有双亲类加载器无法完成此加载任务时,才自己去加载。
注意这里的双亲并不是继承上的关系,而是指的ClassLoader中的一个字段。在源码中更容易描述双亲委派机制:

//java.lang.ClassLoader.java
    // 双亲的定义
    private final ClassLoader parent;
    // 加载方法
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            //如果没有加载过,则进行加载
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 先检查双亲
                    if (parent != null) {  // 如果有双亲,则委派给双亲去加载
                        c = parent.loadClass(name, false);
                    } else { // 没有双亲才使用Bootstrap类加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

从源码中可以看出,双亲为null时才会调用Bootstrap启动类加载器,所以启动类加载器可以当成是最后一个双亲。
ExtClassLoaderAppClassLoader这两个内部类继承自URLClassLoader,而URLClassLoaderSecureClassLoader中仅做了一些检查,双亲委派机制在各个类加载器中是一直有效的。

另外parentprivate final修饰,并且定义的时候没有赋初值,这就必须在构造方法中进行赋值。
我们从源码中分析下各个类加载器的parent到底被赋值为什么。

// java.loang.ClassLoader.java
    // 三个构造方法
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        ...
    }
    // 不指定parent时,默认的parent为系统(System)类加载器,即AppClassLoader
    public static ClassLoader getSystemClassLoader() {
        // 通过java.lang.ClassLoader.getSystemClassLoader()可以直接获取到系统类加载器,后面会通过代码进行测试
        ...
    }
==========================================================================================================
// sun.misc.Launcher.java
    private ClassLoader loader;
    // Launcher 的构造方法
    public Launcher() {
        // Create the extension class loader
        ClassLoader extcl;  // 这里定义了 extcl
        try {
            extcl = ExtClassLoader.getExtClassLoader(); // 调用了内部类ExtClassLoader的方法,生成了ExtClassLoader
        } catch (IOException e) {
            throw new InternalError("Could not create extension class loader");
        }
        // Now create the class loader to use to launch the application
        try {
           loader = AppClassLoader.getAppClassLoader(extcl);  // 调用了内部类的getAppClassLoader方法
        } catch (IOException e) {
            throw new InternalError("Could not create application class loader");
        }
        ...
    }
    // 内部类 AppClassLoader
    static class AppClassLoader extends URLClassLoader {
        // AppClassLoader 的构造方法,所以生成AppClassLoader时必须指定parent
        AppClassLoader(URL[] urls, ClassLoader parent) {
            // 在此设置了 AppClassLoader 的 parent
            super(urls, parent, factory);
        }
        // 内部类的方法
        public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException
        {
            final String s = System.getProperty("java.class.path");
            final File[] path = (s == null) ? new File[0] : getClassPath(s);
            return AccessController.doPrivileged( new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);
                    // 这里指定了 AppClassLoader 的parent为extcl
                    return new AppClassLoader(urls, extcl);  // 生成了AppClassLoader并且设置其parent为extcl
                }
            });
        }
    }
    // 内部类 ExtClassLoader
    static class ExtClassLoader extends URLClassLoader {
        // ExtClassLoader 的构造方法
        public ExtClassLoader(File[] dirs) throws IOException {
            // 调用URLClassLoader的构造方法 URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory)
            // 直接把 ExtClassLoader 的 parent 设置为null
            super(getExtURLs(dirs), null, factory);
        }
    }

通过以上的源码分析可知,在sun.misc.Launcher.java中做了如下工作

  1. 将Launcher默认的类加载器loader设置为AppClassLoader
  2. AppClassLoaderparent设置为ExtClassLoader
  3. ExtClassLoaderparent设置为null

程序验证

验证一: parent的验证

程序源码:

public class TestClassLoader {
    public static void main(String[] args) {
        try {
            System.out.println(ClassLoader.getSystemClassLoader());
            System.out.println(ClassLoader.getSystemClassLoader().getParent());
            System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果为:

sun.misc.Launcher$AppClassLoader@30a4effe
sun.misc.Launcher$ExtClassLoader@1c8825a5
null

这说明AppClassLoader的双亲为ExtClassLoaderExtClassLoader的双亲为null,这跟分析源码得出的结论一致。

验证二: 默认的类加载器

public class TestClassLoader {
    public static void main(String[] args) {
        try {
            // 通过Class.forName加载类
            Class loadedClass = Class.forName("com.test.classloader.TestClassLoader");
            // 查看被加载的类是被那个类加载器加载的
            System.out.println(loadedClass.getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果为:

sun.misc.Launcher$AppClassLoader@30a4effe

这说明系统默认的类加载器为AppClassLoader

验证三: 双亲委派以及类加载器的加载位置

将上面编译好的class文件打包成classloader.jar(最好使用集成工具进行打包),将classloader.jar放到<Java_Runtime_Home >/lib/ext目录下,再次运行,结果成了

sun.misc.Launcher$ExtClassLoader@2e5f8245

这说明通过双亲委派机制,ExtClassLoader抢先加载了类
classloader.jar拷贝一份到<Java_Runtime_Home >/lib目录下,再次运行,结果仍为sun.misc.Launcher$ExtClassLoader@2e5f8245
这说明放到<Java_Runtime_Home >/lib目录下的文件并没有被加载,这跟双亲委派机制并不矛盾。 虚拟机出于安全等因素考虑,不会加载<Java_Runtime_Home>/lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。
做个进一步验证,删除<Java_Runtime_Home>/lib/ext目录下的classloader.jar和工程输出目录下对应的class文件,然后再运行测试代码,则将会有ClassNotFoundException异常抛出。


注意

  1. 在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间, 所以不同类加载器加载的相同类,其类型也是不一样的
  2. 编写自定义类加载器时,不要覆写已有的loadClass(...)方法。
  3. 用户自定义类加载器不指定父类加载器,可以加载<Java_Runtime_Home>/lib下的类, 但不能加载<Java_Runtime_Home>/lib/ext目录下的类。