读懂了class文件之后, 今天来聊一聊加载class的那些事儿(基于Java SE 8)
一、类加载器
在jvm中主要有三大内置类加载器
| 加载器类型 | 实现类 | 加载路径 | 源码位置 |
|---|---|---|---|
| Bootstrap | C/C++实现 | sun.boot.class.path |
hotspot/src/share/vm/... |
| Extension | ExtClassLoader |
java.ext.dirs |
sun.misc.Launcher$ExtClassLoader |
| Application | AppClassLoader |
java.class.path |
sun.misc.Launcher$AppClassLoader |
Bootstrap类加载器
作为jvm最顶层类加载器,jvm实现的一部分,由C/C++实现(HotspotVM是C++)也是唯一一个非ClassLoader子类的加载器,默认情况下负责加载%JAVA_HOME%/jre/lib目录,也可以通过-Xbootclasspath指定加载路径(但这一步有较高的风险性,如果指定加载了与核心jar包同名同路径的class,会造成逻辑破坏)
可以通过getClassLoader()查看类被哪个加载器加载


这段示例代码中输出了两个信息,一是First类本身的类加载器位于rt.jar中的sum.misc.Launcher$AppClassLoader,二则是负责加载核心jar包的BootstrapClassLoader, 但因为Bootstrap是由本地语言实现,所以在java程序中通过getClassLoader()获取其结果显示为null
Extension类加载器
该类加载器是由java代码实现,位于rt.jar中sun.misc.Launcher$ExtClassloader从这里可以发现,他与AppClassLoader同为Launcher的内部类,且都继承自java.net.URLClassLoader这里暂不展开,ExtClassLoader主要负责加载%JAVA_HOME%/jre/lib/ext/目录
比如随便找一个ext下的jaccess.jar中的ButtonTranslator类:


Application类加载器
该类加载器也是java代码实现,主要负责加载classpath目录,也就是我们开发的应用代码的类加载器,比如第一个demo中的First类,其加载器就是AppClassLoader
类加载器层级(实现层面)

这是从实现的角度上Ext/App加载器的关系,从抽象的角度,ClassLoader定义了加载器的规范,SecureClassLoader扩展了安全性,URLClassLoader扩展了通过url加载的能力,而Ext和App则各自分工,那么从逻辑角度,Ext和App包括Bootstrap是如何做到各自分工的呢?
二、双亲委派模型 Parent Delegation Model
基础概念
双亲委派模型是JVM定义的一套默认的类加载机制,当一个类被类加载器加载时,他首先不会自身尝试加载该类,而是把加载请求委托给父加载器去完成,层层往上,只有当所有父加载器都无法完成加载请求时,子加载器才会尝试去加载,其核心概念总结为:自底向上委托,自顶向下加载

源码分析
一、核心初始化流程
1.1 Launcher构建加载器层次
java
ini
public Launcher() {
// 1. 创建ExtClassLoader(父加载器)
ClassLoader extcl = ExtClassLoader.getExtClassLoader();
// 2. 创建AppClassLoader,传入extcl作为parent
loader = AppClassLoader.getAppClassLoader(extcl);
}
关键:必须先创建父加载器,再创建子加载器,构建完整的委托链。
二、ExtClassLoader创建过程
2.1 单例模式获取实例
scss
public static ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
synchronized(ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader(); // 双重检查锁
}
}
}
return instance;
}
2.2 确定扩展加载路径
java
private static ExtClassLoader createExtClassLoader() throws IOException {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
// 获取扩展目录:java.ext.dirs系统属性
final File[] dirs = getExtDirs();
return new ExtClassLoader(dirs); // 创建实例
}
});
}
2.3 ExtClassLoader构造函数
java
public ExtClassLoader(File[] dirs) throws IOException {
// 关键:parent=null,表示父加载器是BootstrapClassLoader
super(getExtURLs(dirs), null, factory);
}
设计要点 :parent=null表示委托链顶端,由BootstrapClassLoader加载。
三、AppClassLoader创建过程
3.1 获取类路径并创建实例
java
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException {
// 获取应用类路径:java.class.path系统属性
final String s = System.getProperty("java.class.path");
final File[] path = getClassPath(s);
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls = pathToURLs(path);
// 关键:传入extcl作为parent参数
return new AppClassLoader(urls, extcl);
}
});
}
3.2 AppClassLoader构造函数
scss
AppClassLoader(URL[] urls, ClassLoader parent) {
// 关键:parent参数是ExtClassLoader实例
super(urls, parent, factory);
}
四、核心设计要点
4.1 委托链完整构建
启动顺序:ExtClassLoader → AppClassLoader
委托链:AppClassLoader → ExtClassLoader → BootstrapClassLoader
4.2 加载范围配置
| 加载器 | 系统属性 | 职责范围 |
|---|---|---|
| ExtClassLoader | java.ext.dirs |
扩展JAR包 |
| AppClassLoader | java.class.path |
应用类路径 |
4.3 关键设计决策
- 单例模式:ExtClassLoader全局唯一
- parent=null:ExtClassLoader父加载器为Bootstrap
- 层次传递:AppClassLoader以ExtClassLoader为parent
- 安全上下文 :通过
AccessController.doPrivileged执行
五、验证委托链
scss
// 运行时验证委托链
ClassLoader appLoader = ClassLoader.getSystemClassLoader();
System.out.println(appLoader); // AppClassLoader
System.out.println(appLoader.getParent()); // ExtClassLoader
System.out.println(appLoader.getParent().getParent()); // null (Bootstrap)
总结:Launcher通过精确的初始化顺序和参数传递,构建了不可变的三层类加载器委托链,这是双亲委派机制能够稳定运行的基石。
六、核心加载过程
双亲委派机制的执行分为两个明确阶段:
1. 委派阶段(递归向上查询)
请求从子加载器发起,通过parent.loadClass()递归调用,沿加载器层次结构向上传递,直至parent为null的BootstrapClassLoader。这一阶段仅进行类查询,不执行实际加载。
2. 加载阶段(逐级向下尝试)
从BootstrapClassLoader开始,每层加载器依次尝试:
findBootstrapClassOrNull()/findClass():在自身责任范围内查找类资源- 若找到资源,则通过
defineClass()→defineClass0()本地方法,将字节码转换为Class对象 - 若未找到,则返回
null或抛出异常,控制权传递到下一级加载器
关键洞察:
- 递归发生在委派阶段,是方法调用层面的递归
- 实际加载发生在返回阶段,是责任链模式的逐级尝试
- 每一层加载器都是先依赖父加载器,失败后才自主加载,确保核心类优先由高层加载器加载
这种设计既保证了Java核心库的安全性(防止用户替换核心类),又通过逐级回退机制实现了类加载的灵活扩展。
三、SPI Service Provider Interface
一、SPI核心概念
1.1 什么是SPI?
SPI是Java提供的一种服务发现机制 ,通过解耦接口与实现,实现可插拔的组件扩展。
1.2 核心思想
markdown
接口定义(标准) ← 配置文件 → 具体实现(扩展)
↑ ↑
JDK 开发者
二、SPI打破双亲委派的原理
2.1 经典问题:JDBC驱动加载
ini
// 传统方式需要显式加载驱动,硬编码且不够灵活
Connection conn = DriverManager.getConnection(new com.mysql.Driver());
// SPI方式:自动发现并加载驱动
Connection conn = DriverManager.getConnection(url);
//DriverManager通过SPI自动发现
2.2 关键类:ServiceLoader
java
// SPI的核心加载器
public final class ServiceLoader<S> implements Iterable<S> {
// 配置文件路径
private static final String PREFIX = "META-INF/services/";
// 加载服务实现
public static <S> ServiceLoader<S> load(Class<S> service) {
// 使用线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
}
2.3 打破双亲委派的关键
scss
// DriverManager的静态初始化块
static {
loadInitialDrivers(); // 驱动加载
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
// 通过ServiceLoader加载驱动,load中传递当前线程上下文的ClassLoader进行处理
ServiceLoader<Driver> loadedDrivers =
ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while (driversIterator.hasNext()) {
driversIterator.next(); // 触发驱动加载和注册
}
}
"SPI机制的核心类ServiceLoader在设计时就固定使用线程上下文类加载器来加载服务实现。当由BootstrapClassLoader加载的DriverManager调用ServiceLoader.load(Driver.class)时,ServiceLoader获取当前线程的上下文类加载器(通常是AppClassLoader,或其他自定义加载器),然后在后续的Class.forName(className, false, loader)中明确指定使用这个加载器来加载驱动类。这样就绕过了双亲委派模型中'父加载器加载的代码不能直接访问子加载器中的类'的限制。"
总结
Java类加载体系是一个精心设计的层次化安全模型,双亲委派是其基石,保障了Java平台的核心稳定性。SPI机制则是这一模型的智慧扩展,通过线程上下文类加载器这一"桥梁",在保持安全的前提下实现了父加载器对子加载器服务的发现。这种"规范中的灵活"设计哲学,使得Java既能坚守类型安全的核心原则,又能支撑起庞大的生态系统,成为企业级应用的首选平台。
理解这一机制不仅有助于解决类加载相关问题,更是深入理解Java平台设计思想、编写高质量可扩展代码的关键。从Class.forName的硬编码到SPI的自动发现,从严格委派到有控制打破,Java类加载机制的演进正是软件工程"关注点分离"、"约定优于配置"等原则的完美体现。