Java 类加载机制双亲委派与自定义类加载器

我们来深入解析 Java 类加载机制。这是理解 Java 应用如何运行、如何实现插件化、以及解决一些依赖冲突问题的关键。

一、核心概念:类加载过程

一个类型(包括类和接口)从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期会经历加载(Loading)、连接(Linking)、初始化(Initialization)、使用(Using)和卸载(Unloading) 五个阶段。其中连接阶段又分为验证(Verification)、准备(Preparation)、解析(Resolution) 三步。

  • 加载: 查找并加载类的二进制字节流(如 .class 文件),并为其在方法区创建一个 java.lang.Class 对象作为访问入口。

  • 验证: 确保被加载的类的正确性和安全性,如文件格式、元数据、字节码、符号引用验证。

  • 准备: 为类变量(static 变量) 分配内存并设置默认初始值(零值)。例如 public static int value = 123; 在此阶段后 value 为 0

  • 解析: 将常量池内的符号引用替换为直接引用的过程。

  • 初始化: 执行类构造器 <clinit>() 方法的过程,该方法由编译器自动收集类中所有类变量的赋值动作静态语句块(static{}块) 中的语句合并产生。此时 value 才会被赋值为 123

重要原则:《Java虚拟机规范》严格规定了有且只有以下六种情况必须立即对类进行“初始化”:

  1. 遇到 newgetstaticputstaticinvokestatic 这四条字节码指令时。

  2. 使用 java.lang.reflect 包的方法对类进行反射调用时。

  3. 当初始化一个类时,发现其父类还未初始化,需先触发其父类的初始化。

  4. 虚拟机启动时,用户指定的主类(包含 main() 方法的那个类)。

  5. 当使用 JDK 7 新加入的动态语言支持时...

  6. 当一个接口中定义了 JDK 8 新加入的默认方法(default方法)时...


二、双亲委派模型 (Parents Delegation Model)

Java 虚拟机如何决定由哪个类加载器来加载一个类?答案就是双亲委派模型。

1. 类加载器层次结构

JVM 提供了三层类加载器:

  • 启动类加载器 (Bootstrap ClassLoader)

    • C++ 实现,是 JVM 自身的一部分。

    • 负责加载 <JAVA_HOME>/lib 目录下的核心类库(如 rt.jarcharsets.jar)或被 -Xbootclasspath 参数指定的路径中的类。

    • 所有类加载器的父加载器

  • 扩展类加载器 (Extension ClassLoader)

    • Java 实现,继承自 ClassLoader

    • 负责加载 <JAVA_HOME>/lib/ext 目录下,或由 java.ext.dirs 系统变量指定的路径中的所有类库。

  • 应用程序类加载器 (Application ClassLoader)

    • Java 实现,继承自 ClassLoader

    • 也叫系统类加载器 (System ClassLoader)

    • 负责加载用户类路径 (ClassPath) 上的所有类库。

    • 是程序中默认的类加载器ClassLoader.getSystemClassLoader() 返回的就是它。

除了这三个,用户还可以自定义类加载器。

2. 双亲委派的工作流程

当一个类加载器收到了类加载请求时,它不会自己去尝试加载,而是会把这个请求委托给父类加载器去完成。每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

流程可以概括为:自底向上检查类是否已加载 -> 自顶向下尝试加载类。

图表

3. 双亲委派的优点
  1. 避免类的重复加载: 确保一个类在 JVM 中全局唯一。无论哪一个类加载器要加载这个类,最终都是委派给最顶层的启动类加载器去加载,从而保证了类的唯一性。

  2. 保证核心 API 的安全: 防止用户自定义一个核心类(例如 java.lang.String)来替换掉 Java 核心库中的类,从而确保了 Java 核心库的安全性和稳定性。例如,即使用户写了一个 java.lang.Object 类并放在 ClassPath 中,最终也只会由 Bootstrap ClassLoader 去加载核心库中的 Object 类,用户的类不会被加载。


三、打破双亲委派模型

双亲委派模型不是一个强制性的约束,而是 Java 设计者推荐给开发者的一种类加载器实现方式。在某些特定场景下,需要打破这个模型。

1. 历史原因:自身的缺陷

双亲委派模型很好地解决了各个类加载器协作时基础类型的一致性问题(越基础的类由越上层的加载器进行加载)。但如果基础类型要调用回用户的代码,双亲委派模型就无法解决了。一个典型的例子就是 JNDI(Java Naming and Directory Interface),它的代码由启动类加载器加载,但需要调用由独立厂商实现并部署在应用程序 ClassPath 下的 JNDI 服务提供者接口(SPI)的代码。启动类加载器不可能“认识”这些用户代码

2. 解决方案:线程上下文类加载器 (Thread Context ClassLoader)

为了解决这个问题,Java 引入了线程上下文类加载器。这个类加载器可以通过 java.lang.Thread.setContextClassLoader() 方法进行设置,如果创建线程时未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那默认就是应用程序类加载器。

SPI(Service Provider Interface)机制(如 JDBC)就利用了这个方案:

  1. java.sql.DriverManager(由 Bootstrap ClassLoader 加载)在初始化时,会通过 ServiceLoader 去加载 java.sql.Driver 接口的实现类。

  2. ServiceLoader 的 load 方法会使用线程上下文类加载器(默认是 AppClassLoader)去加载 META-INF/services 目录下的配置文件中的实现类。

  3. 这样,位于 ClassPath 下的第三方数据库驱动包(如 mysql-connector-java.jar)中的实现类(如 com.mysql.cj.jdbc.Driver)就能被成功加载并注册了。

这个过程可以看作是:父类加载器(Bootstrap)请求子类加载器(AppClassLoader)来完成类加载动作,这种行为实际上打破了双亲委派模型的层次结构,是一种逆向的委托。

3. 其他打破双亲委派的场景
  • 热部署、热替换: 例如 OSGi、JSP 应用服务器(如 Tomcat)。每个模块(Bundle)都有自己的类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉,从而实现代码的热替换。

  • 实现应用隔离: 例如 Tomcat 为每个 Web 应用创建一个独立的 WebappClassLoader,优先加载 /WEB-INF/ 下的类,应用之间互不干扰,这同样打破了双亲委派(它没有先委托给父加载器,而是自己先尝试加载)。


四、自定义类加载器

1. 为什么要自定义?
  • 从非标准来源加载类(如网络、加密的字节流)。

  • 实现类的隔离和热部署。

  • 修改类的加载方式(如打破双亲委派)。

2. 如何实现?

通常不直接重写 loadClass() 方法(因为该方法实现了双亲委派逻辑),而是重写 findClass(String name) 方法

步骤

  1. 继承 java.lang.ClassLoader

  2. 重写 findClass 方法:

    • 在这个方法中,根据指定的类名(如 com.example.MyClass)去查找类的字节码(byte[])。

    • 找到后,调用父类的 defineClass(byte[] b, int off, int len) 方法,将字节数组转换为 Class 对象。

  3. (可选)如果要打破双亲委派,可以重写 loadClass 方法,实现自己的加载逻辑。

示例代码框架

java

public class MyClassLoader extends ClassLoader {private String classPath; // 自定义的类查找路径@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 1. 根据 name 和 classPath,找到 .class 文件,读取为 byte[]byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {// 2. 调用 defineClass,将字节码定义为 Class 对象return defineClass(name, classData, 0, classData.length);}}private byte[] loadClassData(String name) {// 实现从自定义路径(文件、网络等)加载类的字节码的逻辑// 将包名中的 '.' 替换为路径分隔符 '/'String path = name.replace('.', '/').concat(".class");path = classPath + "/" + path;// ... 读取文件并返回 byte[]return data;}
}

总结

概念核心要点
双亲委派模型保证核心类安全、避免重复加载。工作流程:子加载器委托父加载器加载,父加载器无法完成时子加载器才自己加载。
打破双亲委派SPI 等场景需要父加载器请求子加载器完成加载。通过线程上下文类加载器实现,是 Java 生态扩展性的重要基础。
自定义类加载器重写 findClass 方法,从特定来源获取字节码后调用 defineClass。常用于加载非标准来源的类、实现隔离和热部署。

理解类加载机制,尤其是双亲委派和打破它的场景,对于解决日常开发中的 ClassNotFoundException、NoClassDefFoundError、依赖冲突以及构建模块化系统都至关重要。

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

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

相关文章

Kaggle项目实践——Titanic: Machine Learning from Disaster

泰坦尼克号沉船事件是机器学习领域最经典的入门项目之一。Kaggle 上的 Titanic: Machine Learning from Disaster 竞赛&#xff0c;被无数人称为“机器学习的 Hello World”。 一、数据导入与清洗&#xff1a;让数据从 “杂乱” 变 “干净” 机器学习模型就像 “挑食的孩子”…

Qt C++ 复杂界面处理:巧用覆盖层突破复杂界面处理难题​之二

接上一篇&#xff0c;继续探索“覆盖层”的使用方法。 五、覆盖层进阶交互&#xff1a;从 “能绘制” 到 “好操作”​ 基础的绘制功能只能满足 “看得见” 的需求&#xff0c;实际开发中还需要 “能操作”—— 比如选中线条修改颜色、按 Delete 键删除线条、鼠标 hover 时高亮…

神经网络构成框架-理论学习

一、神经网络的基本组成与分类 1.1 神经网络的核心组成部分 神经网络是现代人工智能的基石&#xff0c;其设计灵感来源于生物神经系统的信息处理方式。作为工程师&#xff0c;了解神经网络的基本组成部分对于构建和优化模型至关重要。一个典型的神经网络主要由以下几个关键部分…

从0开始开发app(AI助手版)-架构及环境搭建

架构选择 前端React Native 后端Firebase 原因 环境准备 安装node 安装JDK 命令行工具&#xff1a;Node.js command prompt命令行查询Javav版本&#xff1a;javac -version使用nrm工具切换淘宝源&#xff1a;npx nrm use taobao安装yarn&#xff0c;替代npm下载工具&#x…

【性能测试】Jmeter工具快速上手-搭建压力测试脚本

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【Java】登神长阶 史诗般的Java成神之路 概念 性能测试是软件测试的重要分支&#xff0c;核心目标是通过模拟真实业务场景和负载压力&#xff0c;评估系统在不同条件下的性能表现&#xff0c;发现系统性…

oracle里的int类型

oracle里的int类型 在 ANSI SQL 标准 中&#xff0c;INTEGER 和 SMALLINT 是定义好的精确数值类型&#xff0c;但它们的 “长度”或“大小”并不是通过 (N) 括号来指定的&#xff08;如 INT(4)&#xff09;&#xff0c;这一点与 MySQL 等数据库的非标准扩展完全不同。 SMALLI…

前端学习之后端java小白(二)-sql约束/建表

一、约束SQL约束&#xff08;Constraints&#xff09;是用于限制表中数据的规则&#xff0c;确保数据的完整性和准确性。以下是主要的SQL约束类型&#xff1a; 主要约束类型&#xff1a; 1. NOT NULL 约束: 确保列不能包含空值 CREATE TABLE users (id INT NOT NULL,name VARC…

OpenCV:图像金字塔

文章目录一、什么是图像金字塔&#xff1f;二、图像金字塔的核心操作&#xff1a;采样与逆采样1. 向下采样&#xff08;pyrDown&#xff09;&#xff1a;从高分辨率到低分辨率步骤1&#xff1a;高斯滤波步骤2&#xff1a;删除偶数行与偶数列OpenCV实战代码效果特点2. 向上采样&…

LVS与Keepalived详解(一)负载均衡集群介绍

文章目录前言一、什么是LVS&#xff1f;二、四层&#xff08;L4&#xff09;负载均衡的最佳解决方案是什么&#xff1f;2.1解决方案分类与对比&#xff08;负载均衡的三种方式介绍&#xff09;2.1.1 硬件负载均衡 (Hardware Load Balancer)2.1.2 软件负载均衡 (Software Load B…

消息队列-kafka完结

基本概念和操作 基本概念 简单概念:::color4 Broker&#xff1a;如果将kafka比喻成数据仓库网络&#xff0c;那么Broker就是网络中的仓库节点&#xff0c;比如快递站&#xff0c;在该节点内可以独立运行&#xff0c;并且多个Broker可以连接起来&#xff0c;构建kafka集群Topic&…

Chromium 138 编译指南 Windows篇:环境变量配置与构建优化(三)

引言配置&#xff0c;往往决定成败。在软件开发的世界里&#xff0c;环境变量就像是一位无声的指挥家&#xff0c;默默地协调着各个组件的协同工作。对于Chromium 138这样一个拥有数千万行代码的超大型项目而言&#xff0c;正确的环境变量配置更是编译成功的关键所在。也许您曾…

LabVIEW加载 STL 模型至 3D 场景 源码见附件

LabVIEW 中 STL 模型的导入与 3D 场景显示&#xff0c;基于示例代码逻辑&#xff0c;结合格式兼容性、功能实现步骤及多样化显示方式&#xff0c;适用于三维可视化温控、机械零件模拟等场景。 1示例代码 NI 社区案例 “Add an STL file to 3D scene using LabVIEW” 提供了经…

硅基计划3.0 Map类Set类

文章目录一、二叉搜索树&#xff08;排序树&#xff09;1. 概念初识2. 模拟实现1. 创建搜索树节点2. 查找指定元素是否存在3. 插入4. 删除二、Map类1. put——设置单词以及其频次2. get——获取单词频次3. getOrDefault——获取单词频次或返回默认值4. remove——删除单词频次信…

LeetCode 刷题【73. 矩阵置零】

73. 矩阵置零 自己做 解&#xff1a;标记消除 class Solution { public:void setZeroes(vector<vector<int>>& matrix) {vector<bool> x(matrix.size(), false); //要置0的行vector<bool> y(matrix[0].size(), false); //…

Unity学习----【进阶】TextMeshPro学习(一)--基础知识点

来源于唐老狮的视频教学&#xff0c;仅作记录和感悟记录&#xff0c;方便日后复习或者查找 一.导入TextMeshPro 对于新创建的工程&#xff0c;可以直接在这里导入TMP必要的资源&#xff08;上面&#xff09;&#xff0c;以及TMP的实例和扩展&#xff08;下面&#xff09; 导入之…

BigDecimal(用于处理超出double范围的浮点数)

BigDecimal 是 Java 中 java.math 包提供的高精度十进制浮点数类&#xff0c;专为解决基本类型&#xff08;float/double&#xff09;的精度缺陷而设计&#xff0c;广泛用于金融、科学计算等对精度要求极高的场景。以下从核心特性、使用方法、常见问题对比、注意事项等方面详细…

Nginx 优化

文章目录1、隐藏版本号2、修改用户与组3、缓存时间4、日志切割5、连接超时6、更改进程数7、配置网页8、防盗链1、隐藏版本号 隐藏nginx的版本号&#xff0c;为了防止恶意用户利用已知漏洞进行攻击 ## 查看版本号 curl -I http://192.168.10.23方法一&#xff1a;修改配置文件…

基于多模态与主动学习的车船飞机图像识别系统研究与应用技术方案

技术方案 一、技术背景与研究现状 图像识别是计算机视觉的核心任务之一&#xff0c;随着深度学习的发展&#xff0c;基于 卷积神经网络&#xff08;CNN&#xff09; 与 视觉Transformer&#xff08;ViT&#xff09; 的图像分类方法已成为主流。 根据《图像分类技术选型——截止…

Word2Vec词嵌入技术和动态词嵌入技术

Word2Vec&#xff08;Word to Vector&#xff09;是 2013 年由 Google 团队提出的无监督词嵌入模型&#xff0c;是一种静态词嵌入技术&#xff0c;核心目标是将自然语言中的离散词汇映射为低维、稠密的实数向量&#xff08;即 “词向量”&#xff09;&#xff0c;让向量空间的距…

Netty从0到1系列之Netty逻辑架构【上】

文章目录一、Netty逻辑架构【上】1.1 网络通信层1.1.1 BootStrap & ServerBootStrap1. ✅核心方法链与配置2. ✅ 架构与流程3. ✅ 底层实现与原理分析4. ✅ 实践经验与总结1.1.2 Channel1.2 事件调度层1.2.1 事件调度层概述1.2.2 EventLoop【事件循环】1.2.3 EventLoopGrou…