JVM 类加载器有哪些?双亲委派机制的作用是什么?如何自定义类加载器?
类加载器分类
大家好,我是码哥,可以叫我靓仔,《Redis 高手心法》畅销书作者。
先回顾下,在 Java 中,类的初始化分为几个阶段: 加载
、链接
(包括验证、准备和解析)和 初始化
。
而 类加载器
(Class Loader)则是加载阶段中,负责将本地或网络中的指定类的二进制流,加载到 Java 虚拟机中的工具。

在进入正文前,介绍下我的《Java 面试高手心法 58 讲》专栏,帮助你在现在就业压力巨大的情况下比别人多一个杀手锏,拿下心仪 offer。内容涵盖 Java 基础、Java 高级进阶、Redis、MySQL、消息中间件、微服务架构设计等面试必考点、面试高频点。
丢掉你收藏的那些所谓的「面试宝典」,因为它们大多数深度不够,甚至内容还有错误,你只会看完就忘,还浪费时间。这也是为何每次面试你都回答不好,找不到好工作的原因。
引导类加载器 BootstrapClassLoader
引导类加载器 BootstrapClassLoader:引导类加载器是使用 C++ 语言实现的,嵌入在 JVM 中。用于加载 Java 中的核心类库的,不继承自 java.lang.ClassLoader
,在 Java 程序中通常返回 null
。
一般会加载 JAVA_HOME
目录下的 /jre/lib
文件夹下的 jar 和配置。
ClassLoader loader = String.class.getClassLoader();
System.out.println(loader); // 输出 null,因为 String 是由引导类加载器加载的
扩展类加载器 ExtClassLoader
扩展类加载器主要负责加载 Java 的扩展类库,一般会加载 JAVA_HOME
目录下的 /jre/lib/ext
文件夹下的 jar。
继承自 java.lang.ClassLoader
,是用户可以访问的第一个类加载器。
ClassLoader extLoader = ClassLoader.getSystemClassLoader().getParent();
System.out.println(extLoader); // 输出 sun.misc.Launcher$ExtClassLoader
应用类加载器(Application ClassLoader)
应用类加载器是应用程序中默认的类加载器,可以加载 CLASSPATH
变量指定目录下的 jar,由 sun.misc.Launcher$AppClassLoader
实现。
并且一般情况下,我们编写的 Java 应用的类,都是使用该类加载器完成加载的。
ClassLoader appLoader = ClassLoader.getSystemClassLoader();
System.out.println(appLoader); // 输出 sun.misc.Launcher$AppClassLoader
类加载器抽象类 ClassLoader
在 Java 中存在一个类加载器抽象类 ClassLoader
,大多数类加载器都是通过继承这个类来实现的类加载功能。以下是 ClassLoader 类的关键部分代码:
public abstract class ClassLoader {/** 类加载器的父加载器*/private final ClassLoader parent;/*** 根据类的全限定名加载类** @param name 类名称* @return 加载的Class对象* @throws ClassNotFoundException 没有发现指定类异常*/public Class<?> loadClass(String name) throws ClassNotFoundException {// 调用loadClass方法加载类,其中设置resolve=false,表示不立即解析类return loadClass(name, false);}/*** 根据类的全限定名加载类** @param name 类名称* @param resolve 是否解析这个类,true=解析,false=不解析* @return 加载的Class对象* @throws ClassNotFoundException 没有发现指定类异常*/protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 检查类是否已经被加载Class<?> c = findLoadedClass(name);// 如果没有加载过if (c == null) {// 如果有父类加载器,则委托给父加载器去加载// 如果没有父类加载器,则判断 Bootstrap 类加载器是否加载过if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}// 如果父类加载器都加载失败,则当前类加载器尝试自行加载if (c == null) {c = findClass(name);}}// 据 resolve 参数决定是否解析类if (resolve) {resolveClass(c);}return c;}}/*** 查找并加载指定名称的类** @param name 类名称* @return Class对象* @throws ClassNotFoundException 没有发现指定类异常*/protected Class<?> findClass(String name) throws ClassNotFoundException {//1. 根据传入的类名,到在特定目录下去寻找类文件,把字节码文件读入内存// ...//2. 调用 defineClass 将字节数组转成 Class 对象return defineClass(buf, off, len);}/*** 将一个 byte[] 转换为 Class 类的实例** @param name 类名称,如果不知道此名称,则该参数为 null* @param b 组成类数据的字节数组* @param off 类数据的起始偏移量* @param len 类数据的长度* @return Class对象* @throws ClassFormatError 类格式化异常*/protected final Class<?> defineClass(byte[] b, int off, int len) throws ClassFormatError {...}}
类中定义的常用的类加载相关的方法:
方法名称 | 描述 |
---|---|
getParent() | 返回该类加载器的父类加载器 |
loadClass(String name) | 加载指定名称的类,返回 java.lang.Class 实例 |
findClass(String name) | 查找指定名称的类,返回 java.lang.Class 实例 |
findLoadedClass(String name) | 查找已加载的指定名称的类,返回 java.lang.Class 实例 |
defineClass(String name, byte[] b, int off, int len) | 将字节数组转换为一个 Java 类,返回 java.lang.Class 实例 |
resolveClass(Class c) | 连接指定的 Java 类 |
双亲委派模型(Parent Delegation Model)
双亲委派模型 是类加载器的设计模式,其核心思想是:类加载请求由子类加载器向父类加载器逐层委派,直到引导类加载器。
如果父类加载器无法加载,子类加载器才会尝试加载。
如果子类加载器也无法加载该类,就会抛出一个 ClassNotFoundException
异常。

双亲委派机制的作用
我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的 String 类来动态替代 Java 核心 API 中定义的类型,这样会存在非常大的安全隐患。
而双亲委托的方式,就可以避免这种情况,因为 String 已经在启动时就被引导类加载器 (BootstrcpClassLoader) 加载,所以用户自定义的 ClassLoader 永远也无法加载一个用户自己自定义的 String 类,除非你改变 JDK 中 ClassLoader 搜索类的默认算法。
该机制的作用如下。
防止重复加载字节码文件: 将类加载请求先委托给父类,父类加载后子类就不会重复加载该类。所以,双亲委派机制可以防止对某个类重复加载;
防止核心字节码文件被篡改: 一般情况下引导类加载器会先加载 JVM 核心类库,然后其它加载器才会执行,如果其它加载器要加载一个被篡改的核心字节码文件,会将该文件委托给父类加载器,当委托到引导类加载器时,加载器已经加载过该类,就不会对该类进行重复加载。而且就算能被加载,那么加载它的肯定不是相同的类加载器 (不会是引导类加载器),Java 虚拟机中只认可核心类加载器加载的核心类库,所以,双亲委派机制可以防止核心字节码文件被篡改。
简化加载逻辑: 通过委派模式,每个类加载器只需要关注自己负责的那部分类加载逻辑,而不必关心其他类加载器的加载细节,简化了类加载器的实现,降低了系统的复杂度。
自定义类加载器
在某些场景下,标准的类加载器无法满足需求,例如:
热部署:在 Web 服务器中动态加载或更新类。
模块隔离:在同一个 JVM 中加载不同版本的类。
加密解密:加载经过加密的 Class 文件。
默认的类加载器只能加载指定目录下的 Jar 和 Class 文件。
如果需要加载指定位置的类文件并实现一些自定义逻辑,就需要自定义类加载器。
Chaya:如何实现自定义类加载器?
步骤:
继承
java.lang.ClassLoader
类。重写
findClass()
方法,通过字节流读取 Class 文件并转换为Class
对象。
import java.io.*;public class MyClassLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();}return defineClass(name, classData, 0, classData.length);}private byte[] loadClassData(String name) {String fileName = name.replace('.', '/') + ".class";try (InputStream is = new FileInputStream(fileName);ByteArrayOutputStream baos = new ByteArrayOutputStream()) {int buffer;while ((buffer = is.read()) != -1) {baos.write(buffer);}return baos.toByteArray();} catch (IOException e) {e.printStackTrace();return null;}}
}
示例说明
findClass()
:从文件系统加载 Class 文件,并将其定义为Class
对象。defineClass()
:将字节数组转换为 JVM 可执行的Class
对象。
为了为保证类加载器都正确实现双亲委派机制,在开发自己的类加载器时,只需要重写 findClass()
方法即可。
当然,如果不想使用双亲委派机制时,就需要重写 loadClass()
方法。
打破双亲委派模型
有时为了实现特殊功能,我们需要打破双亲委派模型,例如:
热部署框架:Tomcat、Spring Boot 使用自定义类加载器加载和卸载 Web 应用。
SPI(Service Provider Interface)机制:JDBC 驱动等需要通过
线程上下文类加载器
来加载用户实现的接口。
最后(Ending)
最后,顺带给大家介绍下,我的新书《Redis 高手心法》。
原价 100 元,现在只需要 50。

作者简介
《Redis 高手心法》 作者,InfoQ 签约作者、51CTO Top 红人。拥有 10 年互联网工作经验,作为后端架构师,擅长 Redis、Tomcat、Spring、Kafka、MySQL 技术,对分布式微服务架构有深入了解。
点击卡片,关注我,学技术
往期推荐
面试官拷打:Redis 高可用篇章中面试最常见的 6 个问题!
Redis 7.0 深度探秘:List 数据结构原理与实战指南
重生之从零设计 MySQL 架构
重生之MySQL SQL 执行的 7 大关键步骤,解锁新技能
相关文章:

JVM 类加载器有哪些?双亲委派机制的作用是什么?如何自定义类加载器?
类加载器分类 大家好,我是码哥,可以叫我靓仔,《Redis 高手心法》畅销书作者。 先回顾下,在 Java 中,类的初始化分为几个阶段: 加载、链接(包括验证、准备和解析)和 初始化。 而 类加载器&#x…...

从基态到激发态再到里德伯态的双光子激发过程
铯原子(Cs)从基态6S1/2到激发态6P3/2再到里德伯态44D5/2的双光子激发过程, 并通过数值计算和图形化展示来研究不同失谐条件下的拉比频率、AC Stark位移差以及散射概率的变化 结果显示,在给定的实验参数下,拉比频率较低…...

Clickhouse 外部存储引擎
文章目录 外部存储引擎分类MySQL引擎PostgreSQL引擎MongoDB引擎JDBC引擎ODBC引擎Kafka引擎RabbitMQ引擎File引擎URL引擎HDFS引擎 外部存储引擎分类 引擎类型描述特点MySQL从 MySQL 数据库中读取数据用于与 MySQL 数据库共享数据,支持读取 MySQL 表中的数据 支持 SQ…...

eclipse怎么配置jdk路径?
在Eclipse中配置JDK路径是一个简单的步骤,以下是配置JDK路径的步骤: 打开Eclipse:启动Eclipse IDE。 访问首选项: 在Eclipse的菜单栏中,选择 Window > Preferences(对于Mac OS X用户,选择 E…...

【前端】JavaScript 中的创建对象模式要点
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 💯前言💯对象属性值中的引号规则💯对象属性换行与尾随逗号的使用💯工厂模式:灵活高效的对象创建💯自定义构造函数:通过…...

GWAS分析先做后学
大家好,我是邓飞。 GWAS分析是生物信息和统计学的交叉学科,上可以学习编程,下可以学习统计。对于Linux系统,R语言,作图,统计学,机器学习等方向,都是一个极好的入门项目。生物信息如…...

【系统设计】高可用之缓存基础
缓存的缘起 使用缓存的主要原因包括提高系统性能、降低数据库负载、提升用户体验和保证系统可用性。 在计算机体系结构中,由于处理器和存储器的处理时间不匹配,在处理器和一个较大较慢的设备之间插入一个更小更快的存储设备(如高速缓存&a…...

《Java核心技术I》volatile字段
volatile字段 有多处理器的计算机能够暂时在寄存器或本地内存缓存中保存内存值,其结果是,运行在不同处理器上的线程可能看到同一个内存位置上有不同的值。编译器可以改变指令执行的顺序以使吞吐量更大化,编译器不会选择可能改变代码语义的顺…...

2024运维故障记 | 12/2 网易云音乐崩了
#运维故障记# 前两天看到网易云音乐崩了的新闻,回想了一下,今年从网易云音乐到支付宝、还有微软,近期就发生了好几起运维届的故障。 今年来不及计数了,先做个记录。 明年看看运维届的大故障会发生多少,什么原因&…...

架构设计读后——微服务
1 微服务历史 2005年:Dr. Peter Rodgers提出"Micro-Web-Services"概念2011年:一个软件架构工作组使用"microservice"来描述一中架构模式2012年;这个工作组正式使用"microservice"来代表这个架构2012年&#x…...

软考高级架构-9.4.4-双机热备技术 与 服务器集群技术
一、双机热备 1、特点: 软硬件结合:系统由两台服务器(主机和备机)、一个共享存储(通常为磁盘阵列柜)、以及双机热备软件(提供心跳检测、故障转移和资源管理功能的核心软件)组成。 …...

聊聊前端工程化
深度解析前端工程化 近年来,随着前端技术的快速迭代和项目复杂度的增加,前端开发已经从简单的页面搭建演变为专业的工程化体系。前端工程化通过工具链、标准化和流程化手段,不仅提高了开发效率,也大幅提升了项目的可维护性和协…...

“放弃Redis Desktop Manager使用Redis Insight”:日常使用教程(Redis可视化工具)
文章目录 更新Redis Insight连接页面基础解释自动更新key汉化暂时没有找到方法, Redis Desktop Manager在连接上右键在数据库上右键在key上右键1、添加连接2、key过期时间 参考文章 更新 (TωT)ノ~~~ βyё βyё~ 现在在维护另一…...

mmdection配置-yolo转coco
基础配置看我的mmsegmentation。 也可以参考b站 :https://www.bilibili.com/video/BV1xA4m1c7H8/?vd_source701421543dabde010814d3f9ea6917f6#reply248829735200 这里面最大的坑就是配置coco数据集。我一般是用yolo,这个yolo转coco格式很难搞定&#…...

聚合支付系统/官方个人免签系统/三方支付系统稳定安全高并发 附教程
聚合支付系统/官方个人免签系统/三方支付系统稳定安全高并发 附教程 系统采用FastAdmin框架独立全新开发,安全稳定,系统支持代理、商户、码商等业务逻辑。 针对最近一些JD,TB等业务定制,子账号业务逻辑API 非常详细,方便内置…...

力扣67. 二进制求和
给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。 示例 1: 输入:a "11", b "1" 输出:"100" 示例 2: 输入:a "1010", b "1011" 输出&#…...

网络安全中的 SOC 是什么?
当今世界,网络威胁日益增多,确保网络安全已成为各种规模企业的首要任务。网络安全讨论中经常出现的一个术语是 SOC,即安全运营中心的缩写。但网络安全中的 SOC 是什么呢? SOC在防御网络威胁、管理安全事件和全天候监控系统方面发…...

16、鸿蒙学习——Visibility与(if...else)该如何选择
在鸿蒙中我们如果要控制一个组件的显示与隐藏可以设置组件的Visibility属性,也可使用(if...else)条件控制,具体我们该选择哪个?二者有什么区别呢? 1、Visibility 名称描述Hidden隐藏,但参与布局…...

PH热榜 | 2024-12-05
1. Oopsie 标语:用AI和会话回放调试Flutter和React Native应用 介绍:Zipy推出的Oopsie是一款你唯一需要的AI赋能移动端调试工具,它能提供▶️会话回放、🤖错误监控、💡AI生成的概要分析,以及🔥…...

Qt Chart 模块化封装曲线图
一 版本说明 二 完成示例 此文章包含:曲线轴设置,曲线切换,单条曲线显示,坐标轴。。。 三 曲线图UI创建 在UI界面拖放一个QWidget,然后在 Widget里面放一个 graphicsView 四 代码介绍 1 头文件 #include <QString> #include <QTimer> #include <QMessa…...

【AI系统】MobileFormer
MobileFormer 在本文中,将介绍一种新的网络-MobileFormer,它实现了 Transformer 全局特征与 CNN 局部特征的融合,在较低的成本内,创造一个高效的网络。通过本节,让大家去了解如何将 CNN 与 Transformer 更好的结合起来…...

python数据分析之爬虫基础:解析
目录 1、xpath 1.1、xpath的安装以及lxml的安装 1.2、xpath的基本使用 1.3、xpath基本语法 2、JsonPath 2.1、jsonpath的安装 2.2、jsonpath的使用 2.3、jsonpath的基础语法 3、BeautifulSoup 3.1、bs4安装及创建 3.2、beautifulsoup的使用 3.3、beautifulsoup基本语…...

uniapp中导入uview或者uview plus
关于SCSS uview-plus依赖SCSS,您必须要安装此插件,否则无法正常运行。 如果您的项目是由HBuilder X创建的,相信已经安装scss插件,如果没有,请在HX菜单的 工具->插件安装中找到"scss/sass编译"插件进行安…...

【LeetCode】122.买卖股票的最佳时机II
文章目录 题目链接:题目描述:解题思路一(贪心算法):解体思路二(动态规划): 题目链接: 122.买卖股票的最佳时机II 题目描述: 解题思路一(贪心算法…...

openGauss开源数据库实战十九
文章目录 任务十九 openGauss DML 语句测试任务目标实施步骤一、准备工作二、INSERT语句三、DELETE语句四、UPDATE语句五、清理工作 任务十九 openGauss DML 语句测试 任务目标 掌握DML语句的用法,包括INSERT语句、DELETE语句和UPDATE语句。 实施步骤 一、准备工作 使用Li…...

恶补英语初级第18天,《询问他人的喜好(上)》
对话 Do you like coffee? Yes, I do. Do you want a cup? Yes, please. Do you want any sugar? Yes, please. Do you want any milk? No, thank you. I don’t like milk in my coffee, I like black coffee. Do you like biscuits? Yes, I do. Do you want one? Yes, …...

centos 报 ping: www.baidu.com: Name or service not known
[rootlocalhost ~]$ ping www.baidu.com ping: www.baidu.com: Name or service not known解决办法: 首先要求检查特定文件(/etc/resolv.conf)内是否正确配置了 DNS sudo vim /etc/resolv.conf没有正确配置可以添加如下代码: n…...

Python:使用随机森林分类器进行模型评估:ROC 曲线与 AUC 指标计算
前言 这段代码的目标是使用 随机森林分类器(Random Forest Classifier) 来进行二分类任务,并基于每个数据子集计算 ROC 曲线(Receiver Operating Characteristic Curve)以及 AUC(Area Under Curve…...

数据库表约束完全指南:提升数据完整性和准确性
数据库表约束完全指南:提升数据完整性和准确性 在数据库设计中,表约束是确保数据完整性和准确性的关键工具。本文将详细介绍各种类型的表约束及其使用方法,包括非空约束、唯一约束、主键约束、外键约束、默认值约束、检查约束以及自动递增约…...

【JavaEE】多线程(6)
一、用户态与内核态 【概念】 用户态是指用户程序运行时的状态,在这种状态下,CPU只能执行用户态下的指令,并且只能访问受限的内存空间 内核态是操作系统内核运行时的状态,内核是计算机系统的核心部分,CPU可以执行所有…...