# 4.2 ClassLoader源码解析

有了上面的使用基础,再来分析下类加载器及其重要实现类的源码。

# 4.2.1 loadClass

ClassLoader调用其loadClass方法来加载class,loadClass核心代码如下:

代码位置:src/java.base/share/classes/java/lang/ClassLoader.java

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 首先, 检查类是否已经被加载了
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    // 当前类加载器的父加载不为空,尝试从父类加载器加载
                    c = parent.loadClass(name, false);
                } else {
                    // 父加载器为空,使用启动类加载器加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 忽略异常,继续查找
            }
            if (c == null) {
                // 父加载器加载不到,调用当前类加载器重写的findClass查找
                c = findClass(name);
            }
        }
        // 链接类
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

上面的类加载顺序可以总结为:优先尝试父加载器去加载(如果父加载器为null,则调用系统类加载器BootstrapClassLoader去加载), 父加载器都尝试失败后才会交由当前ClassLoader重写的findClass方法去加载。如下图4-1所示:

图4-1 类加载器的委托模型

图4-1 类加载器的委托模型.png

# 4.2.2 findClass

在加载class的过程中,如果父加载器都没有找到,则调用子类加载器重写的findClass方法继续查找, findClass方法如下:

代码位置:src/java.base/share/classes/java/lang/ClassLoader.java

protected Class<?> findClass(String name) throws ClassNotFoundException {
    // 调用时抛出异常  
    throw new ClassNotFoundException(name);
}
1
2
3
4

可以看到该方法里面抛出异常,因此不能直接调用,需要子类来实现。 URLClassLoader是ClassLoader的子类并重写了findClass方法。 URLClassLoader的属性与构造器如下:

代码位置:src/java.base/share/classes/java/net/URLClassLoader.java

// 类和资源的查找路径
private final URLClassPath ucp;

public URLClassLoader(URL[] urls, ClassLoader parent) {
    // 指定父加载器
    super(parent);
    // ... 权限检查代码省略
    this.acc = AccessController.getContext();
    // 初始化 ucp 属性
    ucp = new URLClassPath(urls, acc);
}
1
2
3
4
5
6
7
8
9
10
11

实现ClassLoader的findClass方法加载指定路径下的类。

代码位置:src/java.base/share/classes/java/net/URLClassLoader.java

protected Class<?> findClass(final String name) throws ClassNotFoundException {
    // 1、将类的全限定名变成.class文件路径的方式
    String path = name.replace('.', '/').concat(".class");
    // 2、在URLClassPath中查找是否存在
    Resource res = ucp.getResource(path, false);
    // ... 异常处理忽略
    return defineClass(name, res);
}
1
2
3
4
5
6
7
8

URLClassLoader的findClass方法的执行逻辑主要分为三步:

  • 将类的全限定名变成.class文件路径的方式;
  • 在URL中查找文件是否存在;
  • 调用defineClass完成类的链接和初始化;

# 4.2.3 defineClass

defineClass与findClass一起使用,findClass负责读取来自磁盘或网络的字节码,而defineClass将字节码解析为Class对象, 在defineClass方法中使用resolveClass方法完成对Class的链接。源代码如下:

代码位置:src/java.base/share/classes/java/lang/ClassLoader.java

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError {
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    // 调用native方法完成链接
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}
1
2
3
4
5
6
7
8
9
10

defineClass的实现在defineClass1方法中,defineClass1是一个native方法,具体实现hotspot中,实现较为复杂,一般不需要特别关注。 ClassLoader加载一个class文件到JVM时需要经过的步骤,如下图4-2所示:

图4-2 JVM加载类的阶段

图4-2 JVM加载类的阶段

一般我们只需要重写ClassLoader的findClass方法获取需要加载的类的字节码,然后调用defineClass方法生成Class对象。如果想要在类加载到JVM中时就被链接,可以调用resolveClass方法,也可以选择交给JVM在类初始化时链接。