类加载机制详解:双亲委派模型与打破它的方式

news2025/5/11 21:41:04

在复杂的 Java 系统中,类加载是最基础却常被忽略的一环。理解 JVM 的类加载机制,特别是 双亲委派模型(Parent Delegation Model),是我们深入掌握热部署、插件机制、ClassLoader 隔离、ClassNotFound 错误等问题的关键。

一、为什么你必须了解类加载机制?

想象几个场景:

  • 使用 Tomcat 热部署时类总是加载失败?
  • SpringBoot 开启 DevTools 后内存暴涨、类冲突?
  • 使用 SPI 扩展接口,却加载不到自定义实现类?

所有这些问题背后,其实都是 类加载机制的问题。理解类是如何被加载、由谁加载、加载优先级如何决定,是中高级 Java 开发者迈向架构能力的必经之路。

二、JVM 中的类加载流程概览

Java 类从被引用到可以使用,需要经过以下 生命周期阶段:

加载(Loading) ➝ 验证(Verification) ➝ 准备(Preparation) ➝ 解析(Resolution) ➝ 初始化(Initialization)

你可以简单理解为:

JVM 读取 .class ➝ 结构校验 ➝ 为静态变量分配内存 ➝ 解析符号引用 ➝ 执行 方法

三、什么是双亲委派模型?

定义

BootstrapClassLoader(引导类加载器)
    ↑
ExtensionClassLoader(扩展类加载器)
    ↑
AppClassLoader(应用类加载器)
    ↑
Custom ClassLoader(自定义类加载器)

加载逻辑伪代码

Class loadClass(String name) {
    // 已加载过,直接返回
    if (已加载类缓存中存在) return;

    // 委托父加载器加载
    if (parent != null) {
        try {
            return parent.loadClass(name);
        } catch (ClassNotFoundException e) {
            // 父类加载器找不到才尝试自己加载
        }
    }

    // 自己加载
    return findClass(name);
}

目的:

  • 防止类重复加载
  • 保证Java核心类的安全性和唯一性
  • 实现类的隔离性

四、演示:双亲委派如何避免核心类被污染?

我们试图编写一个名为 java.lang.String 的类并将其放入 classpath,结果会怎样?

package java.lang;
public class String {
    public String() {
        System.out.println("My Fake String Class");
    }
}

运行结果:

Error: Prohibited package name: java.lang

这是因为:

  • 核心类由 BootstrapClassLoader 先加载
  • 即使你的类也叫 java.lang.String,AppClassLoader 永远加载不到它

五、为什么需要打破双亲委派模型?

尽管双亲委派是安全可靠的,但在实际开发中,它也存在一些限制:
典型场景:

场景说明
热部署/类热替换无法重新加载类,只能加载一次(类缓存)
模块隔离(插件)插件类之间不能相互访问
SPI(服务发现机制)接口在父加载器,实现在子加载器,无法反射加载
动态编译/脚本执行引擎运行时生成类,不能由上层加载器访问

六、打破双亲委派模型的方式

方法一:重写 loadClass() 方法逻辑

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 先尝试自己加载,不委托父类
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            c = findClass(name); // 自己加载
        } catch (ClassNotFoundException e) {
            c = super.loadClass(name, resolve); // 找不到再委托父类
        }
    }
    return c;
}

注意:这样可能会破坏类的唯一性,导致 ClassCastException、类冲突等问题。

方法二:使用多个自定义类加载器做模块隔离

插件系统、脚本引擎常用此法:

ClassLoader pluginLoader1 = new MyClassLoader("pluginA/");
ClassLoader pluginLoader2 = new MyClassLoader("pluginB/");

Class<?> clazz1 = pluginLoader1.loadClass("com.example.Plugin");
Class<?> clazz2 = pluginLoader2.loadClass("com.example.Plugin");

System.out.println(clazz1 == clazz2); // false

不同插件类互相隔离,互不干扰。

七、双亲委派模型的常见陷阱

问题场景说明
类找不到(ClassNotFoundException)类存在但加载器层级错误
类转换异常(ClassCastException)类名相同但加载器不同,导致不兼容
内存泄漏类加载器无法被卸载,常见于容器或热部署场景

八、真实案例分析:Spring Boot DevTools

Spring Boot DevTools 实现类热替换的核心,就是通过 自定义类加载器打破双亲委派模型。

  • 应用类由自定义 RestartClassLoader 加载
  • 每次修改后重新加载类
  • 保证热更新不影响已运行类

九、类加载器在项目中的使用策略

场景建议做法
Web 容器部署避免将第三方 JAR 放入 shared/lib 中,易引发冲突
热部署系统使用隔离 ClassLoader + SPI
插件系统每个插件一个加载器,父加载器只负责接口
工具类封装使用当前线程类加载器(Thread.currentThread().getContextClassLoader())避免硬编码

十、总结

双亲委派模型是 Java 类加载机制的基础设计理念,保护了核心类的安全性与一致性。但在现代开发中,打破这个模型已经成为热部署、插件化架构的必要手段。

开发者要做到:

  • 明确使用哪些加载器
  • 避免无意义的类重复加载
  • 善用隔离加载器做模块隔离
  • 处理好类生命周期,防止泄漏

下一篇预告: 《JVM 调优实战入门:从 GC 日志分析到参数调优》手把手教你理解 GC 日志、如何识别性能瓶颈并合理配置 JVM 参数!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2373441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Redis】C++如何使用redis

文章目录 1. redis客户端2. 使用通用命令3. 使用string命令3. 使用list命令4. 使用set命令5. 使用hash命令6. 使用zset命令 1. redis客户端 在前面的学习种&#xff0c;我们都是使用redis命令行客户端手动执行操作的&#xff1b;但是更多的时候&#xff0c;需要使用redis的api…

考研系列-408真题计算机组成原理篇(2010-2014)

写在前面 此文章是本人在备考过程中408真题计算机组成原理部分(2010年-2014年)的易错题及相应的知识点整理,后期复习也常常用到,对于知识提炼归纳理解起到了很大的作用,分享出来希望帮助到大家~ # 2010年 1.DRAM芯片的排列和编址方式 这个区别于多体交叉编址:这个可以理…

47.电压跌落与瞬时中断干扰的防护改善措施

电压跌落与瞬时中断干扰的防护改善措施 1. 电压跌落与瞬时中断的影响机理2. 解决措施 1. 电压跌落与瞬时中断的影响机理 跌落发生的常见场景如下&#xff1a; &#xff08;1&#xff09;电源插头接触不良&#xff0c;瞬态中断即刻恢复&#xff1b; &#xff08;2&#xff09;电…

极狐Gitlab 里程碑功能介绍

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 里程碑 (BASIC ALL) 极狐GitLab 中的里程碑是一种跟踪议题和合并请求的方法&#xff0c;这些请求是为了在特定时间段内实现更…

一次Android Fragment内存泄露的bug解决记录|Fragment not attach to an Activity

Bug描述 前些天出现了一个 bug。Activity 页面里放了一个 ViewPager2&#xff0c;其中的每一页是一个 Fragment。其中第一页的 Fragment 实现了一个监听器&#xff0c;当事件发生和首次添加到监听器管理者 listener manager 时&#xff0c;manager 会通知所有监听者&#xff0…

LVGL图像导入和解码

LVGL版本&#xff1a;8.1 概述 在LVGL中&#xff0c;可以导入多种不同类型的图像&#xff1a; 经转换器生成的C语言数组&#xff0c;适用于页面中不常改变的固定图像。存储系统中的外部图像&#xff0c;比较灵活&#xff0c;可以通过插卡或从网络中获取&#xff0c;但需要配置…

project从入门到精通(五)

目录 创建资源的基本信息 在project中创建资源工作表 ​编辑信息详解 最大单位 标准费率与加班费率 每次使用成本 成本累算 基准日历 三类资源工作表的总结——不同的资源必须要设置的属性 除了资源名称是必须设置的之外&#xff0c;剩余的资源的可设置选项如下图所…

第3.2.3节 Android动态调用链路的获取

3.2.3 Android App动态调用链路 在Android应用中&#xff0c;动态调用链路指的是应用在运行时的调用路径。这通常涉及到方法调用的顺序和调用关系&#xff0c;特别是在应用的复杂逻辑中&#xff0c;理解这些调用链路对于调试和性能优化非常重要。 1&#xff0c;动态调用链路获…

亿级流量系统架构设计与实战(六)

微服务架构与网络调用 当某个业务从单体服务架构转变为微服务架构后,多个服务之间会通过网络调用形式形成错综复杂的依赖关系。 在微服务架构中 , 一个微服务正常工作依赖它与其他微服务之间的多级网络调用。 网络是脆弱的 , RPC 请求有较大的概率会遇到超时 、 抖动 、 断…

浅聊find_package命令的搜索模式(Search Modes)

背景 find_package应该算是我们使用最多的cmake命令了。但是它是如何找到上游库的.cmake文件的&#xff1f; 根据官方文档&#xff0c;整理下find_package涉及到的搜索模式。 搜索模式 find_package涉及到的搜索模式有两种&#xff1a;模块模式(Module mode)和配置模式(Conf…

【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B

【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B 本地环境说明禁用开源驱动nouveau安装nvidia-smi安装Git环境安装Anaconda(conda)环境下载DeepSeek-R1-Distill-Qwen-7B模型安装LLaMA-Factory下载LLaMA-Factory安装LLaMA-Factory依赖修改环境变量安装deepspeedA…

使用lldb查看Rust不同类型的结构

目录 前言 正文 标量类型 复合类型——元组 复合类型——数组 函数 &str struct 可变数组vec Iter String Box Rc Arc RefCell Mutex RwLock Channel 总结 前言 笔者发现这个lldb挺好玩的&#xff0c;可以查看不同类型的结构&#xff0c;虽然这好像是C的东…

【Linux】线程POSIX信号量

目录 1. 整体学习思维导图 2. 信号量的概念 3. 基本接口 4. 基于环形队列的生产者消费者模型(信号量) 1. 整体学习思维导图 2. 信号量的概念 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的。但 POSIX可以用于线…

MySQL事务和JDBC中的事务操作

一、什么是事务 事务是数据库操作的最小逻辑单元&#xff0c;具有"全有或全无"的特性。以银行转账为例&#xff1a; 典型场景&#xff1a; 从A账户扣除1000元 向B账户增加1000元 这两个操作必须作为一个整体执行&#xff0c;要么全部成功&#xff0c;要么全部失败…

每日脚本学习5.10 - XOR脚本

xor运算的简介 异或就是对于二进制的数据可以 进行同0异1 简单的演示 &#xff1a; 结果是 这个就是异或 异或的作用 1、比较两数是否相等 2、可以进行加密 加密就是需要key 明文 :0b010110 key : 0b1010001 这个时候就能进行加密 明文 ^ key密文 还有这个加密比…

【编译原理】总结

核心 闭包&#xff0c;正则闭包 产生式&#xff08;规则&#xff09; 文法 G[S](&#xff0c;&#xff0c;P&#xff0c;S) 一组规则的集合 &#xff1a;非终结符 &#xff1a;终结符 P&#xff1a;产生式 S&#xff1a;开始符号 推导 归约 规范&#xff08;最右&#xff…

docker创建一个centOS容器安装软件(以宝塔为例)的详细步骤

备忘&#xff1a;后续偶尔忘记了docker虚拟机与宿主机的端口映射关系&#xff0c;来这里查看即可&#xff1a; docker run -d \ --name baota \ --privilegedtrue \ -p 8888:8888 \ -p 8880:80 \ -p 8443:443 \ -p 8820:20 \ -p 8821:21 \ -v /home/www:/www/wwwroot \ centos…

OpenVLA:开源的视觉-语言-动作模型

1. 简介 让我们先来介绍一下什么是OpenVLA&#xff0c;在这里&#xff1a; https://openvla.github.io/ 可以看到他们的论文、数据、模型。 OpenVLA 是一个拥有 70亿参数的开源 **视觉-语言-动作&#xff08;VLA&#xff09;**模型。它是在 Open X-Embodiment 数据集 中的 97万…

Matlab/Simulink的一些功能用法笔记(4)

水一篇帖子 01--MATLAB工作区的保护眼睛颜色设置 默认的工作区颜色为白色 在网上可以搜索一些保护眼睛的RGB颜色参数设置 在MATLAB中按如下设置&#xff1a; ①点击预设 ②点击颜色&#xff0c;点击背景色的三角标符号 ③点击更多颜色&#xff0c;找到RGB选项 ④填写颜色参数…

Elasticsearch:我们如何在全球范围内实现支付基础设施的现代化?

作者&#xff1a;来自 Elastic Kelly Manrique SWIFT 和 Elastic 如何应对基础设施复杂性、误报问题以及日益增长的合规要求。 金融服务公司在全球范围内管理实时支付方面面临前所未有的挑战。SWIFT&#xff08;Society for Worldwide Interbank Financial Telecommunication -…