JVM 类加载器详解

如果要确定两个类是否相同,必须满足以下三点:

  1. 同路径
  2. 同名
  3. 由同一个类加载器创建

即使是同路径同名的 class 类,如果被不同的类加载器加载那也是不同的

一、类加载器分类及作用

JVM 类加载器分为 四类,按层级从上到下依次为:

类加载器 作用 加载路径/范围 实现语言
启动类加载器(Bootstrap ClassLoader) 加载 JVM 核心类库(如 java.lang.*java.util.*),唯一不继承 ClassLoader 的加载器 JAVA_HOME/jre/lib 下的核心 jar 包(如 rt.jar C/C++
扩展类加载器(Extension ClassLoader) 加载 JVM 扩展类库(如 javax.* JAVA_HOME/jre/lib/ext 目录下的 jar 包 Java
应用程序类加载器(Application ClassLoader) 加载用户类路径(ClassPath)下的类(如项目代码、第三方依赖) -classpath-cp 指定的路径 Java
自定义类加载器(Custom ClassLoader) 用户自定义类加载逻辑(如热部署、网络加载、加密类加载) 由用户代码实现 Java

二、双亲委派机制

1. 核心流程

当类加载器收到加载请求时,按以下步骤处理:

  1. 向上委派: 不直接加载类,而是先委派父类加载器处理。
  2. 向下尝试: 若父类加载器无法完成加载(在自己的搜索范围内找不到类),子类加载器才会尝试加载。
2. 流程示例

具体实现依赖于**ClassLoader.loadClass()**

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查是否已加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 2. 先委派父加载器加载
                if (parent != null) {
                    c = parent.loadClass(name, false); // 递归调用父加载器
                } else {
                    // 父加载器为 null,表示到达 Bootstrap 类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父加载器加载失败,继续向下执行
            }

            if (c == null) {
                // 3. 父加载器未找到,由当前类加载器加载
                c = findClass(name);
            }
        }
        return c;
    }
}
  • 用户自定义类 MyClass 的加载过程:
    1. Application ClassLoader → 委派给 Extension ClassLoader
    2. Extension ClassLoader → 委派给 Bootstrap ClassLoader
    3. Bootstrap ClassLoader 未找到 MyClass → 返回 Extension ClassLoader
    4. Extension ClassLoader 未找到 MyClass → 返回 Application ClassLoader
    5. Application ClassLoader 在 ClassPath 中找到并加载 MyClass
3. 核心意义
  • 避免重复加载:确保类在 JVM 中唯一(由父加载器优先加载)。对于避免重复加载,双亲委派通过委派链确保每个类由最顶层的父加载器优先尝试加载,如果已经加载过,就直接返回,不会再让子加载器处理。
  • 保护核心类安全:防止用户自定义同名类(如 java.lang.String)覆盖 JVM 核心类。类加载过程会由启动类加载器强制优先加载核心类,加载后就会直接返回,所以子类无法通过自定义同名类来覆盖核心类

三、打破双亲委派机制的场景

1. 经典场景
  • SPI 服务发现: JDBC 的 DriverManager 需要加载不同厂商的数据库驱动(如 MySQL、Oracle)。
    • 问题:核心类 DriverManager(由 Bootstrap ClassLoader 加载)需调用第三方驱动的实现类(由 Application ClassLoader 加载),但双亲委派机制禁止父加载器访问子加载器的类。
    • 解决方案:将类加载器切换为 线程上下文类加载器,将加载权临时交给子类加载器。
2. 如何打破双亲委派?
  • 重写 loadClass() 方法: 自定义类加载器不委派父加载器,直接加载类。
  • 线程上下文类加载器: 通过 Thread.currentThread().setContextClassLoader() 临时切换类加载器。

以下是java发展历程中的三次打破双亲委派机制

第一次破坏:JDK 1.2 之前的“远古时代”

  • 背景: 在 JDK 1.2 引入双亲委派模型之前,类加载器的实现没有统一规范。开发者通常直接重写 loadClass() 方法,直接实现类加载逻辑,​未形成层级委派机制
  • 问题
    • 类加载逻辑混乱,容易重复加载或覆盖核心类。
    • 缺乏统一的委派规则,导致类加载器之间的协作困难。
  • JDK 1.2 的改进
    • 正式提出双亲委派模型,规范类加载流程。
    • 建议开发者仅重写 findClass()loadClass() 方法默认实现委派逻辑,而 findClass() 仅负责具体类查找(如从自定义路径加载字节码)。开发者只需重写 findClass() 即可实现扩展,无需破坏委派机制。
  • 意义: 通过约束开发者仅扩展 findClass(),避免直接干预委派流程,从而维护双亲委派的核心规则。

第二次破坏:模型自身的缺陷(基础类型回调用户代码)

  • 背景: 双亲委派模型要求父加载器优先加载类,但某些场景下,​父加载器加载的类需要调用子加载器加载的实现类,导致无法直接通过委派机制实现。

  • 典型案例

    JDBC SPI(如 DriverManager):

    • DriverManager(由 Bootstrap ClassLoader 加载)需要加载不同数据库厂商的 Driver 实现类(由 Application ClassLoader 加载)。
    • 按双亲委派规则,父加载器(Bootstrap)无法直接访问子加载器(Application)的类,导致驱动无法加载。
  • 解决方案

    • 线程上下文类加载器(TCCL): 通过 Thread.currentThread().setContextClassLoader() 将类加载器切换为子加载器(如 Application ClassLoader),使父加载器代码可间接访问子加载器的类。
  • 意义: 通过打破双亲委派,解决了核心库(如 JDBC)需要动态扩展的难题,但需谨慎使用以避免安全风险。

第三次破坏:动态性需求(热替换、模块热部署)

  • 背景: 现代应用对 ​动态性 的要求(如不重启服务更新代码)需要更灵活的类加载机制,而双亲委派的层级化模型无法满足。

  • 典型案例

    Tomcat 热部署:

    • 每个 Web 应用使用独立的 WebappClassLoader,重启应用时直接替换类加载器,实现代码热替换。
  • 实现方式

    • 自定义类加载器:直接控制类加载逻辑,绕过双亲委派。
    • 动态卸载与加载:通过销毁旧类加载器并创建新类加载器,实现代码更新。
  • 意义: 牺牲双亲委派的部分安全性,换取系统的高灵活性和动态性,适应云原生、微服务等现代架构需求。

四 、自定义类加载器

使用场景

  • 想加载非 classpath 随意路径中的类文件
  • 通过接口来使用实现,希望解耦时,常用在框架设计和SPI思想
  • 这些类希望予以隔离,不同应用的同名类都可以加载(其实就是违背双委机制的意义1),不冲突,常见于 tomcat 容器

步骤

  1. 继承 ClassLoader 父类
  2. 要遵从双亲委派机制,重写 findClass 方法(不是重写 loadClass 方法,否则不会走双亲委派机制)
  3. 读取类文件的字节码,调用父类的 defineClass 方法来加载类
  4. 使用者调用该类加载器的 loadClass 方法
#java#
fengdongnan的博客 文章被收录于专栏

记录fengdongnan的知识产出文档,欢迎大家来一起交流学习

全部评论

相关推荐

asdasdasdasdas:19岁,不容易啊可能升个本会好点,现在学历歧视太严重了
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
3
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务