类加载器是 JVM 的"搬运工",把字节码搬进内存,把符号变成地址,把类从文件变成可执行的生命体。
类加载器在 JVM 架构中的定位
JVM 的"运行期数据区"里,方法区(JDK 8 以后叫元空间)存放着类的元数据;堆中则躺着 java.lang.Class 对象。连接两者的桥梁正是类加载器(ClassLoader)。没有它,再漂亮的字节码也只是一堆 0 和 1。
一、类加载器定义:三个层次的理解
1. 规范视角
《Java 虚拟机规范》第 5.3 节:
类加载器是一个用于加载类文件的对象,它读取二进制字节流,将其转换为 JVM 内部的数据结构,并生成对应的
java.lang.Class实例。
2. 实现视角
在 JDK 源码里,抽象类 java.lang.ClassLoader 定义了核心契约:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 2. 委派给父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载器无法完成加载,由自身尝试
}
if (c == null) {
// 3. 自己加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}3. 运行时视角
通过 jcmd <pid> VM.classloader_stats 可以查看当前 JVM 中所有类加载器实例及其加载的类数量、占用元空间大小。调试内存泄漏时,这一步往往先于堆 dump。
二、官方分类:三层模型
| 名称 | 加载路径 | 实现语言 | 父加载器 |
|---|---|---|---|
| Bootstrap | %JAVA_HOME%/lib, -Xbootclasspath | C++(JVM 内部) | null |
| Platform | %JAVA_HOME%/lib/ext, java.ext.dirs | Java | Bootstrap |
| Application | -classpath, -cp, MANIFEST.MF 中 Class-Path | Java | Platform |
注:JDK 9 引入模块系统后,ExtClassLoader 被 PlatformClassLoader 取代,但层级思想不变。
三、核心机制:双亲委派模型(Parents Delegation Model)
1. 工作流程
graph TD
A[应用调用 MyClass.class] --> B{AppClassLoader}
B -->|委派| C{PlatformClassLoader}
C -->|委派| D{Bootstrap}
D -->|未找到| C
C -->|未找到| B
B -->|findClass() 读取 MyClass.class| E[定义类]
2. 代码验证
打开 sun.misc.Launcher,可以看到启动时即构造好这条链:
public Launcher() {
// 创建扩展类加载器
ExtClassLoader ext = ExtClassLoader.getExtClassLoader();
// 创建应用类加载器,以扩展类加载器为父
loader = AppClassLoader.getAppClassLoader(ext);
// 设置到线程上下文,供 SPI 使用
Thread.currentThread().setContextClassLoader(loader);
}3. 打破委派:SPI 与线程上下文类加载器
JDBC 4.0 之前,Driver 实现由 rt.jar 里的 java.sql.DriverManager 加载,而具体实现类(如 com.mysql.cj.jdbc.Driver)却在应用 classpath。Bootstrap 无法反向委派给 AppClassLoader,于是引入 线程上下文类加载器(TCCL):
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);ServiceLoader 内部通过 Thread.currentThread().getContextClassLoader() 拿到 TCCL,从而成功加载第三方驱动。