# 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;
}
}
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.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);
}
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);
}
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);
}
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;
}
2
3
4
5
6
7
8
9
10
defineClass的实现在defineClass1方法中,defineClass1是一个native方法,具体实现hotspot中,实现较为复杂,一般不需要特别关注。 ClassLoader加载一个class文件到JVM时需要经过的步骤,如下图4-2所示:
图4-2 JVM加载类的阶段
一般我们只需要重写ClassLoader的findClass方法获取需要加载的类的字节码,然后调用defineClass方法生成Class对象。如果想要在类加载到JVM中时就被链接,可以调用resolveClass方法,也可以选择交给JVM在类初始化时链接。