ClassLoader类加载器源码分析

59 阅读3分钟

概述

对于类的加载器而言,主要是分为以下四类,启动类加载器(boostrapClassLoader),扩展类加载器(ExtClassLoader),应用类加载器(AppClassLoader),自定义类加载器。
对于类加载器而言,通过看源码发现,ExtClassloader和AppClassLoader都是继承于URLClassLoader。

static class ExtClassLoader extends URLClassLoader
static class AppClassLoader extends URLClassLoader

而对于URLClassLoader这个类继承于SecureClassLoader

public class URLClassLoader extends SecureClassLoader

SecureClassLoader这个类继承与ClassLoader

public class SecureClassLoader extends ClassLoader

通常获取类加载器的时候有一个获取当前线程上下文类加载器的接口

Thread.currentThread().getContextClassLoader()

这个类加载器其实是AppClassLoader,原因看如下源码

try {
    loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
    throw new InternalError(
        "Could not create application class loader", e);
}

// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);

1. 双亲委派机制的由来

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);

    // Note: on bugid 4256530
    // Prior implementations of this doPrivileged() block supplied
    // a rather restrictive ACC via a call to the private method
    // AppClassLoader.getContext(). This proved overly restrictive
    // when loading  classes. Specifically it prevent
    // accessClassInPackage.sun.* grants from being honored.
    //
    return AccessController.doPrivileged(
        new PrivilegedAction<AppClassLoader>() {
            public AppClassLoader run() {
            URL[] urls =
                (s == null) ? new URL[0] : pathToURLs(path);
            return new AppClassLoader(urls, extcl);
        }
    });
}

以AppClassLoader为例,在源码中可以看出(代码的19行),把ExtClassLoader最为参数传入了构造AppClassLoader加载器的构造方法中,在这个构造方法内部,最终是把extcl传入到了以下这个属性中

private ClassLoader(Void unused, ClassLoader parent) {
    this.parent = parent;
    if (ParallelLoaders.isRegistered(this.getClass())) {
        parallelLockMap = new ConcurrentHashMap<>();
        package2certs = new ConcurrentHashMap<>();
        assertionLock = new Object();
    } else {
        // no finer-grained lock; lock on the classloader instance
        parallelLockMap = null;
        package2certs = new Hashtable<>();
        assertionLock = this;
    }
}

从上面的代码看出,是把extcl作为参数传入到了parent属性中。同时对于ExtClassLoader这个扩展类加载器的构造方法中的源码可以看出

public ExtClassLoader(File[] dirs) throws IOException {
    super(getExtURLs(dirs), null, factory);
    SharedSecrets.getJavaNetAccess().
        getURLClassPath(this).initLookupCache(this);
}

parent属性是null值传入到构造方法中的,因此才说getParent()值为null的时候表示这是个bootstrap类加载器。

2. 双亲委派机制的执行过程

为了查看双亲委派机制的执行过程,以下查看ClassLoader类中的loadClass源码

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 {
                    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;
    }
}

分为以下几点重点解释:

  • 通过查看代码的第4行,可以看出类加载是一个线程安全的加载过程,因为加上了Synchronized重量级锁。
  • 在进行加载之前,首先查看内存中是否已经加载过这个类,避免重复加载,这体现在代码的第6行findLoadedClass。
  • 当发现内存中并没有加载过这个类的时候,就会进行加载,可以看代码的第10行到第14行,先采用这个类加载器的parent加载进行加载,这样重复调用的结果是在ExtClassLoader中的parent为null,因此会进入findBootstrapClass,这也就是为什么说null对应着启动类加载器。
  • 当父类加载器无法加载的时候,c会被置为null,此时会进入到当前加载器的findClass方法,采用当前类的加载器进行加载。
  • 代码的32行到35行,这个resolve是这个类是否被解析的开关。
  • 在findclass这个方法中是真正进行类加载的方法,在源码可以看出里面执行了defineClass方法。
private Class<?> defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    URL url = res.getCodeSourceURL();
    if (i != -1) {
        String pkgname = name.substring(0, i);
        // Check if package already loaded.
        Manifest man = res.getManifest();
        definePackageInternal(pkgname, man, url);
    }
    // Now read the class bytes and define the class
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        // Use (direct) ByteBuffer:
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, bb, cs);
    } else {
        byte[] b = res.getBytes();
        // must read certificates AFTER reading bytes.
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, b, 0, b.length, cs);
    }
}