Java 类加载器(ClassLoader)是 Java 运行时环境(JRE)的核心组成部分,负责将 Java 类动态加载到 Java 虚拟机(JVM)中。由于类加载器的存在,Java 运行时系统无需直接了解底层的文件或文件系统细节
Java 类并非在程序启动时一次性全部加载到内存中,而是按需加载——即当应用程序首次使用某个类时,JRE 才会调用相应的 ClassLoader 将该类加载进内存
这种机制使 ClassLoader 在 Java 的动态类加载能力中扮演了关键角色,不仅提升了内存使用效率,还增强了应用程序的灵活性和可扩展性
Java 的类加载器(ClassLoaders)分为多种类型,每种负责从特定位置加载类:
启动类加载器(Bootstrap ClassLoader,又称 Primordial ClassLoader)
启动类加载器由本地机器代码(通常是 C/C++ 实现)构成,是 JVM 启动时最早运行的组件
在 Java 8 及更早版本中,它从 rt.jar 等核心 JAR 文件中加载 Java 基础类库(如 java.lang.*、java.util.* 等)
从 Java 9 开始,由于引入了模块化系统(Jigsaw),核心类库被打包进 Java 运行时镜像(Java Runtime Image, JRT),启动类加载器改从此处加载模块化的平台类
它没有父类加载器,在类加载器层次结构中处于最顶层
平台类加载器(Platform Class Loader)
在 Java 9 之前,这一角色由 扩展类加载器(Extension ClassLoader) 承担,负责加载 $JAVA_HOME/jre/lib/ext 目录下的扩展 JAR
自 Java 9 起,扩展机制被弃用,该加载器更名为 平台类加载器(Platform Class Loader),用于加载 JDK 模块系统中的平台模块(如 java.sql、java.xml 等)
它从 Java 运行时镜像或通过 --module-path 或系统属性(如 java.platform)指定的模块路径中加载类
系统类加载器(System ClassLoader,又称应用类加载器 Application ClassLoader)
负责加载应用程序自身的类,即开发者编写的代码以及第三方依赖
它是平台类加载器的子加载器
加载路径由以下方式指定:
环境变量 CLASSPATH
命令行参数 -classpath 或 -cp
默认当前目录(若未显式指定)
这种分层结构遵循 双亲委派模型(Parent Delegation Model):当一个类加载器收到加载请求时,会先委托其父加载器尝试加载,只有在父加载器无法完成时,才由自己加载。这一机制保障了 Java 核心类库的安全性和一致性
Java 类加载器(ClassLoaders)的运作遵循以下核心原则:
类加载器采用双亲委派机制(Parent Delegation Model):
当 JVM 需要加载某个类时,首先检查该类是否已加载。若未加载,则从应用类加载器(Application ClassLoader)开始,逐级向上委托——先交由其父加载器(平台类加载器),再由平台类加载器委托给启动类加载器(Bootstrap ClassLoader)
每个类加载器首先尝试让其父加载器加载类;只有当父加载器无法找到该类时,当前加载器才会尝试自己加载
这一机制确保了核心 Java 类(如 java.lang.Object)始终由最上层的 Bootstrap ClassLoader 加载,防止被用户自定义类篡改,从而保障系统安全与一致性
可见性原则(Visibility Principle)
子类加载器可以访问由其父类加载器加载的类,但父类加载器无法访问子类加载器加载的类
例如,应用类加载器加载的类可以使用 java.util.ArrayList(由 Bootstrap 或 Platform 加载),但反之则不行
此设计增强了模块隔离性,避免不同来源的同名类相互干扰
唯一性原则(Uniqueness Property)
每个类在 JVM 中由“类加载器实例 + 全限定类名”共同唯一标识
由于委托机制的存在,一个类只会被最先能够加载它的类加载器加载一次,后续请求直接返回已加载的类,避免重复加载和潜在冲突
java.lang.ClassLoader 中提供了多个用于加载类的方法:
loadClass(String name, boolean resolve):由 JVM 调用以加载所引用的类,并在必要时进行解析(resolve)
defineClass():将字节数组定义为一个类的实例;如果该字节数组不符合有效的类文件格式,则抛出 ClassFormatError
findClass(String name):查找指定名称的类,但不触发其加载(通常由子类重写以实现自定义加载逻辑)
findLoadedClass(String name):检查指定名称的类是否已被当前类加载器加载
Class.forName(String name, boolean initialize, ClassLoader loader):使用指定的 ClassLoader 加载并(可选)初始化类。若传入的 ClassLoader 为 null,则默认使用 Bootstrap 类加载器
示例:
// 在类加载前执行的代码
Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("com.example.MyClass");Java 虚拟机(JVM)和 ClassLoader 使用一种称为委托层次算法(Delegation Hierarchy Algorithm)的机制来加载类。其工作流程如下:
ClassLoader 始终遵循委托层次原则。
当 JVM 遇到一个类时,首先检查该类是否已加载到方法区(Method Area)。
如果已加载,则直接继续执行。
如果未加载,则 JVM 通知 ClassLoader 子系统加载该类,并将控制权交给 Application ClassLoader(也称 System ClassLoader)。
Application ClassLoader 并不立即尝试加载类,而是将请求向上委托给其父加载器:
首先委托给 Extension ClassLoader;
Extension ClassLoader 再委托给 Bootstrap ClassLoader。
Bootstrap ClassLoader 在 Bootstrap 类路径(通常是 JDK/jre/lib 或模块路径)中查找类:
若找到,则加载该类;
若未找到,则将控制权交还给 Extension ClassLoader。
Extension ClassLoader 在 扩展类路径(如 JDK/jre/lib/ext 或通过 java.ext.dirs 指定的目录)中查找:
若找到,则加载;
否则,再将控制权交还给 Application ClassLoader。
Application ClassLoader 最后在 应用程序类路径(即 -classpath 或 -cp 指定的路径)中查找:
若找到,则加载;
若仍未找到,则抛出 ClassNotFoundException。
注意:从 Java 9 开始,
jre/lib/ext目录和java.ext.dirs系统属性已被弃用,Extension ClassLoader 的作用大幅弱化,但委托模型的基本逻辑仍保持不变
该原则规定:
父加载器加载的类对子加载器可见;
子加载器加载的类对父加载器不可见。
例如,若 Hello.class 由 Extension ClassLoader 加载,则 Application ClassLoader 可以访问它,但 Bootstrap ClassLoader 无法访问。如果 Bootstrap ClassLoader 尝试再次加载该类,将因找不到而抛出 java.lang.ClassNotFoundException
该原则确保:
每个类在 JVM 中只被加载一次;
子加载器不会重复加载已被父加载器成功加载的类。
具体而言,只有当父加载器无法找到某个类时,当前 ClassLoader 实例才会尝试自己加载。这避免了类的重复定义和潜在的类型冲突(如 ClassCastException 或 LinkageError)
在 JVM 请求加载某个类后,需遵循一系列步骤完成类的加载。尽管整个过程基于委托模型(Delegation Model),但以下几个核心方法在类加载机制中起着关键作用:
loadClass(String name, boolean resolve)
该方法用于加载 JVM 所引用的类,接收类的全限定名作为参数。若 resolve 为 true,则在加载后还会对类进行解析(例如链接阶段中的符号引用解析)。这是 ClassLoader 的入口方法,通常不应被子类重写
defineClass()
这是一个 final 方法,不可被重写。它将一个字节数组(代表有效的 .class 文件内容)转换为 Class 对象。如果字节码不符合 JVM 规范,则抛出 ClassFormatError
findClass(String name)
此方法用于查找并定义指定名称的类,但不触发委托或解析。通常由自定义 ClassLoader 重写此方法,以实现特定的类查找逻辑(如从网络、加密文件等加载字节码)
findLoadedClass(String name)
检查当前 ClassLoader 是否已加载过指定名称的类。若已加载,则直接返回对应的 Class 对象,避免重复加载
Class.forName(String name, boolean initialize, ClassLoader loader)
该静态方法不仅加载类,还可选择是否执行初始化(即执行 <clinit> 静态初始化块)。通过 loader 参数可显式指定使用哪个 ClassLoader。若传入 null,则默认使用 Bootstrap ClassLoader(注意:此时返回的 Class 对象的 getClassLoader() 将为 null)
以下代码展示了 loadClass 方法的标准流程(在 Java 8 及更早版本中常见):
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 1. 检查类是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 2. 委托给父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 若无父加载器,则委托给 Bootstrap ClassLoader
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 父加载器未找到类,继续由当前加载器处理
}
// 3. 若父加载器未加载成功,则调用 findClass 自行加载
if (c == null) {
c = findClass(name);
}
}
// 4. 若需要解析,则进行解析
if (resolve) {
resolveClass(c);
}
return c;
}
说明:
如果类已加载,直接返回
否则,优先委托给父 ClassLoader(遵循委托模型)
仅当父加载器无法加载时,才调用
findClass()由当前加载器尝试加载最后根据
resolve参数决定是否调用resolveClass()进行解析