一、核心概念总览

1. 什么是类加载器(ClassLoader)

定义:Java 虚拟机(JVM)中用于加载 .class 字节码文件到内存中,并生成对应的 java.lang.Class 对象的组件。核心作用

  • 读取本地 / 网络 / 数据库等任意来源的字节码文件
  • 确保类的唯一性、安全性
  • 实现类的热部署、模块化加载等高级功能

2. 类加载的生命周期(简版)

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载类加载器只负责「加载」阶段,后续步骤由 JVM 自动完成。


二、Java 内置三种类加载器

JVM 自带三层类加载器,层级结构从上到下

1. 启动类加载器(Bootstrap ClassLoader)

  • 最顶层,由 C++ 实现,无对应 Java 对象
  • 加载核心类库$JAVA_HOME/jre/lib 下的核心包(rt.jar、resources.jar 等)
  • 加载 java.lang.*java.util.* 等基础类
  • 无法被 Java 代码直接获取(获取结果为 null

2. 扩展类加载器(Extension ClassLoader)

  • 由 Java 实现(sun.misc.Launcher$ExtClassLoader
  • 加载扩展类库$JAVA_HOME/jre/lib/ext 目录下的 jar 包
  • 父加载器:启动类加载器

3. 应用程序类加载器(App ClassLoader)

  • 默认系统类加载器sun.misc.Launcher$AppClassLoader
  • 加载项目 classpath 下的所有类(我们写的业务类、第三方 jar 包)
  • 父加载器:扩展类加载器
  • 可通过 ClassLoader.getSystemClassLoader() 获取

层级关系图

plaintext

启动类加载器(Bootstrap)
        ↑
扩展类加载器(Extension)
        ↑
应用程序类加载器(App)
        ↑
自定义类加载器(Custom)

三、双亲委派模型(核心原理)

1. 定义

当一个类加载器收到类加载请求时:

  1. 自己不先加载,把请求向上委托给父类加载器
  2. 所有加载器依次向上委托,直到启动类加载器
  3. 若父加载器无法加载(找不到类),才由子加载器尝试加载

一句话总结:向上委托,向下加载

2. 核心源码(ClassLoader.loadClass()

java

运行

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查类是否已经加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    // 2. 委托给父类加载器加载(核心!)
                    c = parent.loadClass(name, false);
                } else {
                    // 3. 父类为空,使用启动类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父类加载失败,自己加载
            }
            if (c == null) {
                // 4. 父类无法加载,自己调用 findClass 加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

3. 双亲委派模型的三大优势

  1. 避免类重复加载保证一个类在 JVM 中只存在一个 Class 对象
  2. 保证核心类安全防止恶意代码替换 Java 核心类(如自定义 java.lang.String 无法被加载)
  3. 层级清晰、职责分离核心类、扩展类、业务类分层加载,便于管理

四、破坏双亲委派模型(面试高频)

1. 什么是破坏?

不遵循「向上委托」规则,类加载器直接自己加载类,绕过父类。

2. 为什么要破坏?

  • 热部署(热更)
  • 模块化隔离(SPI、OSGi、Spring Boot)
  • 不同模块需要加载同名不同版本的类

3. 常见破坏场景

  1. 重写 loadClass() 方法(不推荐)
  2. SPI 机制(JDBC、JNDI)
  3. Tomcat 类加载器
  4. OSGi 模块化框架

五、实战:自定义类加载器

1. 实现步骤

  1. 继承 java.lang.ClassLoader
  2. 重写 findClass() 方法(遵守双亲委派)
  3. 读取字节码文件 → 调用 defineClass() 生成 Class 对象

2. 完整代码

java

运行

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;

/**
 * 自定义文件系统类加载器
 * 遵守双亲委派模型
 */
public class CustomClassLoader extends ClassLoader {

    // 类文件所在路径
    private final String classPath;

    public CustomClassLoader(String classPath) {
        this.classPath = classPath;
    }

    /**
     * 重写 findClass 方法(核心)
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        try {
            // 1. 读取 .class 文件字节码
            byte[] classData = loadClassData(className);
            
            // 2. 将字节码转为 Class 对象(JVM  native 方法)
            return defineClass(className, classData, 0, classData.length);
        } catch (Exception e) {
            throw new ClassNotFoundException("类加载失败:" + className, e);
        }
    }

    /**
     * 从文件系统读取字节码
     */
    private byte[] loadClassData(String className) throws Exception {
        // 类名转文件路径:com.test.User → com/test/User.class
        String path = classPath + "/" + className.replace(".", "/") + ".class";

        try (FileInputStream fis = new FileInputStream(path);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            return bos.toByteArray();
        }
    }

    // 测试
    public static void main(String[] args) throws Exception {
        // 指定 class 文件目录
        CustomClassLoader loader = new CustomClassLoader("D:/project/target/classes");
        
        // 加载类
        Class<?> clazz = loader.loadClass("com.example.demo.TestClass");
        
        // 输出类加载器
        System.out.println("类加载器:" + clazz.getClassLoader());
        System.out.println("父加载器:" + clazz.getClassLoader().getParent());
    }
}

3. 关键方法说明

表格

方法 作用
loadClass() 实现双亲委派逻辑,不要轻易重写
findClass() 自定义加载逻辑,推荐重写
defineClass() 将字节码转为 Class 对象,native 方法

六、面试高频考点(必背)

1. 三种类加载器及其职责?

  • 启动类加载器:加载 JRE 核心类
  • 扩展类加载器:加载 ext 目录扩展类
  • 应用类加载器:加载项目 classpath 类

2. 双亲委派模型流程?

收到请求 → 向上委托 → 顶层无法加载 → 向下加载。

3. 双亲委派的好处?

防重复加载、保证核心类安全、职责清晰。

4. 如何自定义类加载器?

继承 ClassLoader,重写 findClass(),调用 defineClass()

5. 哪些场景破坏了双亲委派?

SPI、Tomcat、OSGi、热部署。

6. 为什么 Tomcat 要破坏?

一个服务器运行多个 Web 应用,不同应用可使用不同版本的第三方包,需要隔离。


七、总结

  1. 类加载器:负责把 .class 加载进内存生成 Class 对象
  2. 三层结构:启动 → 扩展 → 应用 → 自定义
  3. 双亲委派:向上委托,向下加载,保证安全与唯一性
  4. 自定义加载器:重写 findClass(),遵守模型更安全
  5. 破坏模型:为了隔离、热更、模块化,是高级应用场景
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐