一、什么是 ClassLoader?
ClassLoader(类加载器) 是 Java 虚拟机(JVM)用来将 .class 字节码文件加载到内存中,并生成对应的 java.lang.Class 对象的组件。
📌 核心职责:
加载(Loading):通过类的全限定名(如
java.lang.String)获取其二进制字节流;链接(Linking):
验证(Verification):确保字节码符合 JVM 规范;
准备(Preparation):为静态变量分配内存并设默认值;
解析(Resolution):将符号引用转为直接引用;
初始化(Initialization):执行
<clinit>方法(静态代码块和静态变量赋值)。
✅ 本质:ClassLoader 是 Java 实现“动态性”和“模块化”的基础机制。
二、ClassLoader 的层次结构(双亲委派模型)
Java 采用 双亲委派模型(Parent Delegation Model) 来组织类加载器,避免重复加载和安全风险。
1. 内置类加载器(从上到下)
🔍 注意:Bootstrap ClassLoader 在 Java 中无法直接获取(返回
null)。
2. 双亲委派工作流程

流程说明:
当前 ClassLoader 收到加载请求;
先委托父加载器尝试加载;
若父加载器无法加载(未找到),自己才尝试加载;
若都失败,抛出
ClassNotFoundException。
✅ 优点:
避免重复加载(如
java.lang.Object不会被用户自定义加载器重复加载);保证核心类安全(防止用户伪造
java.lang.String)。
三、如何获取当前类的 ClassLoader?
public class ClassLoaderDemo {
public static void main(String[] args) {
// 获取当前类的 ClassLoader(通常是 AppClassLoader)
ClassLoader appLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("App ClassLoader: " + appLoader);
// 获取 String 类的 ClassLoader(核心类,由 Bootstrap 加载)
ClassLoader stringLoader = String.class.getClassLoader();
System.out.println("String ClassLoader: " + stringLoader); // null
// 获取系统默认 ClassLoader
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("System ClassLoader: " + systemLoader);
// 获取父加载器
ClassLoader parent = appLoader.getParent();
System.out.println("Parent of AppLoader: " + parent); // ExtClassLoader
}
}输出示例:
App ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
String ClassLoader: null
System ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
Parent of AppLoader: sun.misc.Launcher$ExtClassLoader@6bc7c054四、自定义 ClassLoader(核心用法)
当需要从非标准位置(如网络、数据库、加密文件)加载类时,可继承 ClassLoader 并重写 findClass() 方法。
示例:从本地目录加载 .class 文件
import java.io.*;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
// 重写 findClass 方法(推荐方式,而非 loadClass)
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException("Class not found: " + name);
}
// 调用 defineClass 将字节数组转为 Class 对象
return defineClass(name, classData, 0, classData.length);
}
// 从指定路径读取 .class 文件为字节数组
private byte[] loadClassData(String className) {
String fileName = classPath + File.separatorChar +
className.replace('.', File.separatorChar) + ".class";
try (FileInputStream fis = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int data;
while ((data = fis.read()) != -1) {
baos.write(data);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}使用自定义 ClassLoader
// 假设有一个编译好的 Hello.class 文件放在 /tmp 目录下
public class Main {
public static void main(String[] args) throws Exception {
MyClassLoader myLoader = new MyClassLoader("/tmp");
// 加载 Hello 类(注意:不能与当前 ClassLoader 已加载的类同名)
Class<?> clazz = myLoader.loadClass("Hello");
// 创建实例并调用方法
Object obj = clazz.getDeclaredConstructor().newInstance();
clazz.getMethod("say").invoke(obj); // 输出: Hello from custom ClassLoader!
}
}⚠️ 关键点:
不要重写
loadClass()(会破坏双亲委派),应重写findClass();使用
defineClass()将字节码转为Class对象;自定义类不能与已加载类同名(否则由原 ClassLoader 返回)。
五、ClassLoader 的扩展应用场景
1. 热部署 / 热加载
Web 容器(如 Tomcat)为每个 Web 应用创建独立的
WebAppClassLoader;修改类文件后,销毁旧 ClassLoader,新建 ClassLoader 重新加载,实现“不重启更新”。
2. 插件化架构
主程序通过自定义 ClassLoader 动态加载插件 JAR 包;
插件之间隔离,避免类冲突。
3. 代码加密与保护
将
.class文件加密存储;自定义 ClassLoader 在加载时解密字节码。
4. OSGi 模块化
OSGi 框架(如 Apache Felix)为每个 Bundle 提供独立 ClassLoader,实现模块隔离与依赖管理。
六、破坏双亲委派模型的场景
虽然双亲委派是默认机制,但某些场景需要主动打破它:
场景 1:SPI(Service Provider Interface)
如 JDBC:
DriverManager是核心类(由 Bootstrap 加载),但具体数据库驱动(如com.mysql.Driver)是用户提供的。
解决方案:使用 线程上下文类加载器(Thread Context ClassLoader):
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
Class<?> driverClass = contextLoader.loadClass("com.mysql.cj.jdbc.Driver");场景 2:Tomcat 的 WebAppClassLoader
优先加载 Web 应用自身的类(如
WEB-INF/classes),而不是委托给父加载器;避免不同应用之间的类冲突。
七、常见问题与注意事项
✅ 最佳实践:
避免随意打破双亲委派;
自定义 ClassLoader 时注意资源释放;
插件系统中做好类隔离。