JVM专题五:类加载器与双亲委派机制

通过上一篇Java的类加载机制相信大家已经搞明白了整个类加载从触发时机,接着我们就来看下类加载器,因为类加载机制是有加载器实现的。 

类加载器的分类

启动类加载器

Bootstrap ClassLoader 是 Java 虚拟机(JVM)的一部分,它负责加载 Java 核心库,也就是 Java Runtime Environment (JRE) 中的类。这些类通常位于 JRE 的 lib 目录下的 rt.jar 文件中,以及可能的其他 JAR 文件,比如 jsse.jar(Java Secure Socket Extension)等。

Bootstrap ClassLoader 是一个虚拟的类加载器,它不继承自 java.lang.ClassLoader,也不可以被直接引用或实例化。它是 JVM 的一部分,并且是所有类加载器的父加载器。当 JVM 启动时,Bootstrap ClassLoader 首先加载 rt.jar 中的类,然后这些类可以被其他类加载器使用。

Bootstrap ClassLoader 的主要作用包括:

  1. 加载 Java 核心库,如 java.lang 包中的类。
  2. 作为类加载器层次结构的根,为其他类加载器提供基础。

扩展类加载器

Extension ClassLoader 是 Java 虚拟机(JVM)中的一个系统类加载器,它负责加载 Java 扩展目录中的类库。以下是关于 Extension ClassLoader 的几个关键点:

  1. 加载职责:Extension ClassLoader 专门用来加载 lib/ext 目录或者由系统属性 java.ext.dirs 指定的其他目录中的类库。这些类库通常包括 Java 平台的标准扩展,例如一些常用的第三方库。

  2. 类加载顺序:在 JVM 的类加载体系中,Extension ClassLoader 位于 Bootstrap ClassLoader 之后。当一个类请求被提交到 JVM 时,Bootstrap ClassLoader 首先尝试加载,如果找不到相应的类,请求会传递给 Extension ClassLoader。

  3. 双亲委派模型:Extension ClassLoader 遵循 Java 的双亲委派模型,这意味着在尝试自己加载类之前,它会先委托给父类加载器(在这个情况下是 Bootstrap ClassLoader)进行加载。

  4. 配置灵活性:开发者可以通过设置 java.ext.dirs 系统属性来指定多个扩展目录,使得 JVM 可以在启动时从这些目录中加载类库。

应用类加载器

Application ClassLoader 是 JVM 中的一个系统类加载器,它的作用是加载应用程序类路径(ClassPath)上指定的类库。以下是 Application ClassLoader 的几个关键点:

1. 加载职责:Application ClassLoader 主要负责加载用户编写的 Java 应用程序代码。这些代码通常位于项目的 `bin` 或 `classes` 目录下,或者是通过 JAR 文件提供的。

2. 类加载顺序:在 JVM 的类加载器层级中,Application ClassLoader 位于 Extension ClassLoader 之后。这意味着,如果一个类同时在扩展目录和应用程序类路径中存在,Extension ClassLoader 会优先加载它。

3. 双亲委派模型:和 Extension ClassLoader 一样,Application ClassLoader 也遵循双亲委派模型。当它需要加载一个类时,会先委托给父类加载器(Extension ClassLoader)尝试加载,如果父类加载器无法加载,Application ClassLoader 才会尝试从应用程序类路径加载。

4. 灵活性:开发者可以通过设置系统属性 `java.class.path` 或使用 `-cp` 或 `-classpath` 命令行选项来指定应用程序类路径,从而控制 Application ClassLoader 加载的类库。

简而言之,Application ClassLoader 是我们程序员在 Java 应用程序开发过程中接触最多的类加载器,它负责将编写的 Java 类加载到 JVM 中,是 Java 应用程序运行的基础。其实你大致就理解为去加载你写好的Java代码,这个类加载器就负责加载你写好的那些类到内存里

自定义类加载器

自定义类加载器是 Java 动态加载类的一种强大机制,它允许开发者根据特定的需求来加载类。这种机制特别有用在需要动态加载或更新类定义的场景中,例如在热部署、模块化应用、或者需要从非标准源加载类文件等情况下。以下是 自定义类加载器的几个关键点:

  1. 继承性:自定义类加载器需要继承 java.lang.ClassLoader 类,并重写 findClass 方法来实现自定义的类查找逻辑。

  2. 加载逻辑:开发者可以在 findClass 方法中实现自己的类加载逻辑,例如从数据库、网络、文件系统等非标准位置加载类文件。

  3. 双亲委派模型:自定义类加载器同样遵循 Java 的双亲委派模型。在尝试加载类之前,它会委托给父类加载器,如果父类加载器无法加载,自定义类加载器才会介入。

  4. 隔离性:自定义类加载器可以创建与系统类加载器和应用程序类加载器隔离的类,这有助于实现模块化和防止类冲突。

  5. 安全性:自定义类加载器可以提供额外的安全检查,例如验证类文件的来源或内容,确保加载的类是安全的。

  6. 灵活性:通过自定义类加载器,开发者可以控制类的加载时机、来源和方式,为 Java 应用程序提供更高的灵活性和可扩展性。

  7. 使用场景:自定义类加载器适用于需要动态加载类、实现类版本控制、或者需要加载特定格式类文件的应用程序。

        通过自定义类加载器,Java 应用程序可以突破传统的类加载限制,实现更加灵活和动态的类加载策略。这对于需要高度定制化和模块化的应用程序尤其重要。下面提供简单的代码实例:

import java.io.*;
import java.nio.file.*;
import java.util.logging.*;public class MyClassLoader extends ClassLoader {private static final Logger LOGGER = Logger.getLogger(MyClassLoader.class.getName());private final String classPath;private final boolean verify;public MyClassLoader(String classPath) {this(classPath, true);}public MyClassLoader(String classPath, boolean verify) {this.classPath = classPath;this.verify = verify;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {String fileName = name.replace('.', '/') + ".class";byte[] classData = loadClassData(fileName);if (classData == null) {throw new ClassNotFoundException("Could not find " + fileName + " in " + classPath);}if (verify) {verifyClassData(classData);}return defineClass(name, classData, 0, classData.length);}private byte[] loadClassData(String fileName) throws IOException {Path path = Paths.get(classPath, fileName);try (InputStream in = Files.newInputStream(path)) {return in.readAllBytes();}}private void verifyClassData(byte[] classData) {// 这里可以添加对类数据的验证逻辑LOGGER.info("Class data verification is not implemented yet.");}public static void main(String[] args) {try {String classPath = "path/to/your/classes"; // 替换为实际的类文件路径MyClassLoader myClassLoader = new MyClassLoader(classPath);Class<?> myClass = myClassLoader.loadClass("com.example.MyClass");Object instance = myClass.getDeclaredConstructor().newInstance();// 假设 MyClass 有一个 sayHello 方法java.lang.reflect.Method sayHelloMethod = myClass.getMethod("sayHello");sayHelloMethod.invoke(instance);} catch (Exception e) {LOGGER.log(Level.SEVERE, "Error loading class", e);}}
}

双亲委派机制

双亲委派机制层级结构

JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器,虽然说是最后一层,但是因为我们可以无限自定义,所以其实就是树的结构,深度可以不断累加。如下图:

为什么需要双亲委派机制?

双亲委派机制是 Java 类加载机制的核心原则之一,其设计具有以下几个重要目的:

  1. 安全性:双亲委派机制确保了 Java 核心库的类不能被随意替换或篡改。因为只有引导类加载器(Bootstrap ClassLoader)才能加载 rt.jar 中的 Java 核心类库,而它是不可扩展的。这防止了恶意代码对 Java 核心库的破坏。

  2. 避免类的重复加载:通过委托给父类加载器,JVM 确保了一个类在 JVM 内只被加载一次。这避免了类的重复加载,节省了内存资源,并且保证了类的唯一性。

  3. 保证加载顺序:双亲委派模型保证了类按照既定的顺序加载,例如,Java 核心库总是首先加载,然后是扩展库,最后是应用程序类。这有助于维护程序的稳定性和可预测性。

  4. 封装性和层次性:双亲委派模型允许 Java 应用程序分层,每一层都有自己的类加载器。这有助于实现模块化,使得不同层次的代码可以独立地更新和替换,而不会影响到其他层次。

  5. 类空间隔离:在复杂的应用程序中,如 Web 容器或 OSGi 框架,双亲委派模型通过使用不同的类加载器来实现类空间的隔离。这使得不同的模块或应用可以加载相同类的不同版本,而不会相互冲突。

  6. 易于实现和维护:双亲委派模型的实现相对简单,逻辑清晰,易于理解和维护。开发者可以专注于实现自己的 findClass 方法,而不必担心类加载的委托逻辑。

  7. 灵活性:虽然双亲委派模型是 Java 类加载的默认策略,但它并不是强制性的。开发者可以通过自定义类加载器来实现特定的类加载逻辑,例如,从数据库或网络加载类。

  8. 优化性能:通过缓存已加载的类(在 ClassLoader 中的 classes 集合中),JVM 可以快速检查类是否已经被加载,从而避免不必要的加载过程,提高性能。

简而言之,双亲委派机制为 Java 类加载提供了一种安全、有效、可预测的策略,有助于维护 Java 应用程序的稳定性和性能。

Tomcat真的破双亲委派机制了嘛?

Tomcat 并没有打破 Java 的双亲委派模型,而是在双亲委派模型的基础上进行了适应 Web 容器需求的扩展。下面是几个关键点来说明这一点:

  1. 双亲委派模型的核心:双亲委派模型的核心是,当一个类加载器收到类加载请求时,它会先委托给父类加载器去尝试加载这个类,直到达到启动类加载器。如果父类加载器无法完成加载任务,才会由子类加载器尝试加载。

  2. Tomcat 类加载器的实现:Tomcat 实现了多个层次的类加载器,包括 Common ClassLoader、Server ClassLoader、Shared ClassLoader 和 Webapp ClassLoader。这些类加载器都遵循双亲委派模型的委派逻辑。

  3. Webapp ClassLoader 的特殊性:虽然 Tomcat 的 Webapp ClassLoader 允许 Web 应用加载自己版本的类,但这是通过实现自己的 findClass 方法来实现的,而不是通过重写 loadClass 方法来绕过双亲委派模型。Webapp ClassLoader 仍然会首先委托父类加载器尝试加载类。

  4. 线程上下文类加载器(TCCL):Tomcat 在执行请求时,会将当前 Web 应用的类加载器设置为 TCCL。这确保了请求处理过程中加载的类首先会使用 Webapp ClassLoader,但这个过程仍然是在双亲委派模型的框架内进行的。

  5. 隔离性和灵活性:Tomcat 的类加载器设计提供了类隔离和版本控制的灵活性,这并不违反双亲委派模型,而是扩展了类加载器的使用场景。

类加载器实际的关系

        双亲委派机制,最早我一直以为是子类继承父类,其实看到源码以后发现实际上并没有继承关系,而是有成员变量取名叫parent,所以‘亲’由此而来;他们之间是组合关系并非是继承嗷!

        说到这里,本来该结束了,突然想到Effective Java中有一条原则是:组合优于继承?有小伙伴知道吗?哈哈这里就不展开了,后续会在设计模式模块好好唠唠。

结合上述类加载器,回头看看类加载机制忘了的小伙伴可以看上篇文章Java的类加载机制,整体流程如下图:

现在我们已经了解到了如何把文件加载到JVM里了,那从JVM角度考虑这些类的数据应该存放到什么位置呢?运行时候产生的动态数据又该放到哪里呢?

 专题汇总

JVM专题一:深入分析Java工作机制

JVM专题二:Java如何进行编译的

JVM专题三:Java代码如何运行

JVM专题四:JVM的类加载机制

JVM专题五:类加载器与双亲委派机制

JVM专题六:JVM的内存模型

JVM专题七:JVM垃圾回收机制

JVM专题八:JVM如何判断可回收对象

JVM专题九:JVM分代知识点梳理

JVM专题十:JVM中的垃圾回收机制

JVM专题十一:JVM 中的收集器一

JVM专题十二:JVM 中的收集器二

JVM专题十三:总结与整理(面试常用)

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

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

相关文章

斜光测距的原理及python实现

1.前言 最近做了一个基于opencv的斜光测距的小项目&#xff0c;东西不多&#xff0c;但是很有意思&#xff0c;值得拿出来学一学。项目里面需要比较精确的定位功能&#xff0c;将前人matlab代码移植到python上&#xff0c;并且做了一些优化&#xff0c;简化逻辑(毕竟我是专业的…

我教会了我妈搭建自己的 AI 聊天机器人...

在这个人工智能爆发的年代,ChatGPT、Claude、Kimi、文心一言等 AI 大模型产品火遍全网,仿佛一夜之间,人人都在谈论 AI。 作为普通人的我们,难道就只能看着程序员们尽情玩耍,自己却无法参与其中吗?NO! 鉴于最近自己社群学员和粉丝的要求&#xff0c;一进来大家无论是不是小白…

Linux防火墙【SNAT,DNAT】

NAT: 支持PREROUTING&#xff0c;INPUT&#xff0c;OUTPUT&#xff0c;POSTROUTING四个链 请求报文&#xff1a;修改源/目标IP&#xff0c; 响应报文&#xff1a;修改源/目标IP&#xff0c;根据跟踪机制自动实现 NAT的实现分为下面类型&#xff1a; SNAT&#xff1a;source…

如何实现数字人系统私有化部署?数字人源码部署教程简易版来了!

当前&#xff0c;数字人行业的市场前景和应用潜力不断显现&#xff0c;不少创业者都想要通过学习数字人源码部署教程来搭建属于自己的数字人系统&#xff0c;以此获得进军数字人行业的入场券。而事实上&#xff0c;该想法本身当然是固然值得鼓励的&#xff0c;但就目前的实践情…

基于改进天鹰优化算法(IAO)优化RBF神经网络数据回归预测 (IAO-RBF)的数据回归预测(多输入多输出)

改进天鹰优化算法(IAO)见&#xff1a;【智能优化算法】改进的AO算法(IAO)-CSDN博客 代码原理 基于改进天鹰优化算法&#xff08;IAO&#xff09;优化RBF神经网络数据回归预测&#xff08;IAO-RBF&#xff09;的多输入多输出&#xff08;MIMO&#xff09;数据回归预测&#xf…

视频去水印软件?在线去除视频水印工具网站?

视频去水印软件哪个好&#xff1f;在数字时代&#xff0c;视频内容的传播变得日益普遍&#xff0c;然而&#xff0c;许多视频带有水印&#xff0c;影响了观看体验&#xff0c;如果有这些图案我们需要找方法把这些图案从视频上去掉。今天我们就来看一下视频去水印的超级简单的方…

添加阈值滞后以实现平滑的欠压/过压锁定

电阻分压器将高压衰减到低压电路可以承受的水平&#xff0c;而不会过驱动或损坏。在电源路径控制电路中&#xff0c;电阻分压器有助于设置电源欠压和过压锁定阈值。这种电源电压鉴定电路存在于汽车系统、电池供电的便携式仪器以及数据处理和通信板中。 欠压锁定 (UVLO) 可防止…

goLang小案例-打印99乘法表

goLang小案例-打印99乘法表 1. 打印99乘法表 func Print99multiplication1() {//横向9排for i : 1; i < 9; i {//竖向9列//第一批第一个 和第一列比较 如果大于排就结束//假设第三排i3 最走有三列 1*3 2*3 3*3//j3打印完 j 当j4就要结束 以此类推for j : 1; j < i; j …

蓝牙耳机推荐学生党怎么选?300左右蓝牙耳机分享

在选择蓝牙耳机时&#xff0c;学生党需要考虑的因素包括音质、续航力、舒适度以及连接稳定性等&#xff0c;而在300元左右的价位&#xff0c;虽然不能期待与高端产品相媲美的性能&#xff0c;但依然有一些性价比较高的选择能够满足大部分的日常需求&#xff0c;下面给大家推荐几…

使用python基于经纬度获取高德地图定位地址【逆地址解析】

一、高德地图api申请 1. 高德开放平台注册&#xff0c;登录 进入网址&#xff1a;高德开放平台 | 高德地图API 注册 -- 支付宝扫码认证 -- 完善个人信息 -- 登录 2. 申请API &#xff08;1&#xff09;点击头像 -- 应用管理 -- 我的应用 -- 创建新应用 &#xff08;2&…

【papaparse插件】前端预览csv文件

需求&#xff1a;就是可以在前端直接预览csv文件的内容 1.了解csv文件 1.1 csv文件和xlsx、xls文件的异同 首先了解一下csv文件和excel文件&#xff08;xlsx&#xff0c;xls&#xff09;有什么异同&#xff0c;简单来说他们都是存储表格数据的文件&#xff0c;csv只能显示较…

一站式AI服务平台:MaynorAI助您轻松驾驭人工智能

一站式AI服务平台&#xff1a;MaynorAI助您轻松驾驭人工智能 在当前的数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术已经成为推动各行各业创新发展的核心动力。为了更好地满足企业和个人用户对AI服务的需求&#xff0c;MaynorAI 作为一个领先的一站式调用国内…

【oracle】oracle索引分裂

本文为云贝教育 刘峰 原创&#xff0c;请尊重知识产权&#xff0c;转发请注明出处&#xff0c;不接受任何抄袭、演绎和未经注明出处的转载。 一、索引分裂概述 Oracle数据库中的索引分裂是数据库维护和性能管理中的一个重要概念&#xff0c;特别是在处理大量数据插入、更新和…

CVPR 2024最佳论文分享:生成图像动力学

CVPR 2024最佳论文分享&#xff1a;生成图像动力学 CVPR&#xff08;Conference on Computer Vision and Pattern Recognition&#xff09;是计算机视觉领域最有影响力的会议之一&#xff0c;主要方向包括图像和视频处理、目标检测与识别、三维视觉等。近期&#xff0c;CVPR 2…

2000—2022年青藏高原遥感生态指数数据集

该数据集是基于多套MODIS数据集&#xff0c;选取NDVI、LST、WET、NDBSI四项指标&#xff0c;采用主成分分析法&#xff0c;生成2000-2022年500米空间分辨率的遥感生态指数&#xff08;RSEI&#xff09;数据集。 遥感生态指数&#xff1a;是一种基于遥感技术的生态环境质量综合评…

3D立体卡片动效(附源码)

3D立体卡片动效 欢迎关注&#xff1a;xssy5431 小拾岁月参考链接&#xff1a;https://mp.weixin.qq.com/s/9xEjPAA38pRiIampxjXNKQ 效果展示 思路分析 需求含有立体这种关键词&#xff0c;我们第一反应是采用动画中的平移、倾斜等实现。如果是立体&#xff0c;必然产生阴影&…

[c++] 类型推导 typeid decltype auto

前言:为什么c需要类型推导 诸如python这类语言,声明对象和函数返回时都不需要显示声明类型,如果程序比较复杂很容易在函数多级传参时找不到返回值的数据类型,这时类型推导就是一件很有意义的事了,而c/c中,函数的返回值类型都是显示传递的,不同类型之间还不能直接进行赋值,看起…

FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析

一、引言 由《音视频入门基础&#xff1a;H.264专题&#xff08;3&#xff09;——EBSP, RBSP和SODB》可以知道&#xff0c;H.264 码流中的操作单位是位(bit)&#xff0c;而不是字节。因为视频的传输和存贮是十分在乎体积的&#xff0c;对于每一个比特&#xff08;bit&#xf…

利用圆上两点和圆半径求解圆心坐标

已知圆上两点P1&#xff0c;P2&#xff0c;坐标依次为 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1​,y1​),(x2​,y2​)&#xff0c;圆的半径为 r r r&#xff0c;求圆心的坐标。 假定P1&#xff0c;P2为任意两点&#xff0c;则两点连成线段的中点坐标是 x m i …

Git Flow 工作流学习要点

Git Flow 工作流学习要点 Git Flow — 流程图Git Flow — 操作指令优点&#xff1a;缺点&#xff1a;Git Flow 分支类型Git Flow 工作流程简述关于 feature 分支关于 Release 分支关于 hotfix 分支 总结 Git Flow — 流程图 图片来源&#xff1a;https://nvie.com/posts/a-succ…