类加载器与双亲委派

类加载器与双亲委派

Java 类加载器(Class Loader)是 Java 虚拟机(JVM)的一部分,负责将类的字节码加载到内存中,并将其转换为可执行的 Java 对象。类加载器在 Java 应用程序中起着重要的作用,它实现了动态加载类的机制,使得 Java 具备了灵活性和可扩展性。

1. 概念

1.1 类加载的过程

类的生命周期通常包括:加载、链接(验证、准备、解析)、初始化、使用和卸载。其中类加载的三个阶段为:加载、链接(验证、准备、解析)、初始化、

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HXXOMaTP-1692809690523)(img/1-1类加载的阶段.png)]

其作用分别为:

  • 加载:通过一个类的完全限定查找类字节码文件,转化为方法区运行时的数据结构,创建一个代表该类的 Class 对象。
  • 链接:
    • 验证:确保 Class 文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。
    • 准备:为类变量(即 static 修饰的字段变量)分配内存并且设置该类变量的初始值。不包含被 final 修饰的 static 变量,因为它在编译时已经分配了。
    • 解析:将常量池内的符号引用转换为直接引用的过程。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载。
  • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

1.2 Java 类加载器

Java 类加载器:

Java 虚拟机用于加载类文件的一种机制。在 Java 中,每个类都由类加载器加载,并在运行时被创建为一个 Class 对象。类加载器负责从文件系统、网络或其他来源中加载类的字节码,并将其转换为可执行的 Java 对象。类加载器还负责解析类的依赖关系,即加载所需的其他类。

虚拟机内部提供了三种类加载器:

  • 启动(Bootstrap)类加载器:也称为根类加载器,它负责加载 Java 虚拟机的核心类库,如 java.lang.Object 等。启动类加载器是虚拟机实现的一部分,它通常是由本地代码实现的,不是 Java 类。
  • 扩展(Extension)类加载器:用来加载 Java 扩展类库的类加载器。扩展类库包括 javax 和 java.util 等包,它们位于 jre/lib/ext 目录下。
  • 系统(System)类加载器:也称应用类加载器,它负责加载应用程序的类。它会搜索应用程序的类路径(包括用户定义的类路径和系统类路径),并加载类文件。

用户可以自定义类加载器。

1.3 双亲委派模型:加载类

类加载器采用了双亲委派模型(Parent Delegation Model)来加载类。即当一个类加载器需要加载类时,它会首先委派给其父类加载器加载。如果父类加载器无法加载,才由该类加载器自己去加载。这种层级关系使得类加载器能够实现类的共享和隔离,提高了代码的安全性和可靠性。

1.3.1 双亲委派模型的执行流程

双亲委派模型的执行流程:

1、当加载一个类时,会先从应用程序类加载器的缓存里查找相应的类,如果能找到就返回对象,如果找不到就执行下面流程;
2、在扩展加载器缓存中查找相应的类,如果能找到就返回对象,如果找不到就继续下面流程;
3、在启动类加载器中查询相应的类,如果找到就返回对象,如果找不到就继续下面流程;
4、在扩展加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就继续下面流程;
5、在应用程序类加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就返回 ClassNotFound 异常。

当一个类加载器收到了一个类加载请求时,它自己不会先去尝试加载这个类,而是把这个请求转交给父类加载器

双亲的含义:

  • 向上查找缓存并加载
  • 向下加载类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bC8GY7tP-1692809690524)(img/1-3-1双亲委派模型.png)]

ClassLoader 内的 loadClass 方法中的双亲委派实现:

    protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {//检查该class是否已经被当前类加载器加载过,这是一个抽象方法,具体实现由子类实现Class<?> c = findLoadedClass(name);if (c == null) {//此时该class还没有被加载try {if (parent != null) {//如果父加载器不为null,则委托给父类加载c = parent.loadClass(name, false);} else {//如果父加载器为null,说明当前类加载器已经是启动类加载器,直接时候用启动类加载器去加载该classc = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}if (c == null) {//此时父类加载器都无法加载该class,则使用当前类加载器进行加载long t1 = System.nanoTime();c = findClass(name);...}}//是否需要连接该类if (resolve) {resolveClass(c);}return c;}}

1.3.2 优点

安全

用户无法伪造不安全的系统类。根据双亲委派模型,jre 提供的类在启动类和扩展类加载时加载早于用户伪造的系统类的应用类加载。

避免重复加载

当一个类加载后,会被缓存,不会出现多个类加载器将同一个类重复加载的情况。

1.3.3 缺点

加载 SPI 实现类的场景

类加载的范围受到限制,某些情况下父 class loader 无法加载某些类文件,这时候就需要委托到下层级的 class loader 去加载类文件。

双亲委派使得启动类加载器无法加载用户的 jar 包,比如:

  • 装载 JDBC 驱动实现类的 DriverManager 类是 JDK 核心类,而被装载的类是用户类,导致无法加载的尴尬问题,需要用 Context Class Loader 来加载 Driver 实现类,从而打破了双亲委派模型。
  • 在 tomcat 中,子加载器优先于父加载器加载。即为了实现各个 webapp 的隔离性,webappClassLoader 会先于父加载器加载。

2. 自定义类加载器

参考 ClassLoader 的实现流程,需要依次实现:

  1. loadclass:双亲委派机制,子加载器委托父加载器加载,父加载器都加载失败时,子加载器通过 findclass 自行加载。该方法使用了模版方法模式,继承 ClassLoader 后,不需要我们实现。
  2. findclass:当前类加载器根据路径以及 class 文件名称加载字节码,从 class 文件中读取字节数组,然后使用 defineClass
  3. defineclass:根据字节数组,返回 Class 对象。

在 ClassLoader 的源码中,提供了一个自定义类加载器的模版:

      class NetworkClassLoader extends ClassLoader {String host;int port;public Class findClass(String name) {// 读取class文件,转化为字节数组byte[] b = loadClassData(name);// 读取字节数组,转化为Class对象return defineClass(name, b, 0, b.length);}private byte[] loadClassData(String name) {// load the class data from the connection}}

可以看到只需要继承 ClassLoader,并且重写 findClass 方法即可。

3. JDBC 打破双亲委派

jdk 为了统一管理数据库驱动,在 java.sql 下定义了 Driver 接口,具体的实现由数据库厂商去做。

3.1 JDBC 4.0 之前

        // 使用应用类加载器加载 Driver 实现类Class.forName("com.mysql.jdbc.Driver");Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {//将mysql的Driver注册进驱动管理器中DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
}

加载 Driver 实现类的过程:

  1. Class.forName 会使用应用类加载器加载 Driver 实现类
  2. 加载 Driver 实现类需要执行静态方法,即将 mysql 的 Driver 注册进驱动管理器中,那么此时需要加载 DriverManager 类
  3. 应用类加载器去加载 DriverManager 类,而 DriverManager 位于 rt.jar 中,便一直向上委托到启动类加载器完成加载

没有破坏双亲委派

3.2 JDBC 4.0 之后

        // 直接调用 DriverManager获取驱动列表Enumeration<Driver> en = DriverManager.getDrivers();while (en.hasMoreElements()) {java.sql.Driver driver = en.nextElement();System.out.println(driver);}

应用类加载器逐层委托到启动类加载器去加载 DriverManager 时,会同时执行它的静态方法

    static {loadInitialDrivers();println("JDBC DriverManager initialized");}

启动类加载 DriverManager,之后需要通过 spi 机制去加载 jar 包中的 Driver 类,而该 Driver 理应被应用类加载器加载,这个时候就需要启动类加载器去通知应用类加载器,这明显违背了双亲委派机制。

loadInitialDrivers 方法:

    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();

ServiceLoader.load 方法,Thread.currentThread().getContextClassLoader()是线程上下文类加载器,使用的是线程上下文类加载器去加载的 Driver 实现类。

    public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}

在 sun.misc.Launcher 类中,将应用类加载器设置进了线程上下文类加载器中,通过线程上下文类加载器,我们可以拿到应用类加载器的引用:

    public Launcher() {this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);Thread.currentThread().setContextClassLoader(this.loader);}

打破双亲委派的过程:
在 jdbc4.0 的情况下,梳理一下整个过程:

  1. 应用类加载器逐层委托到启动类加载器去加载 DriverManager 类
  2. 启动类加载器加载 DriverManager 类时,会执行其静态方法,即通过 spi 机制去加载 jar 包中的 Driver 实现类
  3. 此时启动类加载器需要委托应用类加载器加载 Driver 实现类,具体做法是通过线程上下文类加载器拿到应用类加载器的引用

4. TOMCAT 打破双亲委派

TOMCAT 的两个基础功能点:

  • 实时更新 JSP
  • 应用打包放在 webapps 目录下就可以运行

4.1 实时更新 JSP

JVM 确定类的唯一性:由类加载器实例+全限定名一起确定的。全限定名相同,类加载器不同,则会被认定为不同的类。

4.2 应用打包放在 webapps 目录下就可以运行

应用 A 和应用 B 所依赖的 Spring 版本不同,却依旧可以运行。

webapps 下的每一个应用都会对应一个不同的类加载器实例,用以保持应用间的隔离。

4.3 打破双亲委派

某个自定义的类加载想要打破双亲委派,只需要重写 loadClass 方法即可。

Tomcat 中的 WebappClassLoader 就是自定义类加载器,它的 loadClass 方法为:

    public Class loadClass(String name) throws ClassNotFoundException {return (loadClass(name, false));}public Class loadClass(String name, boolean resolve)throws ClassNotFoundException {if (log.isDebugEnabled())log.debug("loadClass(" + name + ", " + resolve + ")");Class clazz = null;// Log access to stopped classloaderif (!started) {try {throw new IllegalStateException();} catch (IllegalStateException e) {log.info(sm.getString("webappClassLoader.stopped", name), e);}}//1、从自己的本地缓存中查找,本地缓存的数据结构为ResourceEntryclazz = findLoadedClass0(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Returning class from cache");if (resolve)resolveClass(clazz);return (clazz);}//2、从jvm的缓存中查找clazz = findLoadedClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Returning class from cache");if (resolve)resolveClass(clazz);return (clazz);}//3、如果缓存中都找不到,则利用系统类加载器加载try {clazz = system.loadClass(name);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {// Ignore}if (securityManager != null) {int i = name.lastIndexOf('.');if (i >= 0) {try {securityManager.checkPackageAccess(name.substring(0,i));} catch (SecurityException se) {String error = "Security Violation, attempt to use " +"Restricted Class: " + name;log.info(error, se);throw new ClassNotFoundException(error, se);}}}boolean delegateLoad = delegate || filter(name);//4、开启代理的话,则使用父加载器加载if (delegateLoad) {if (log.isDebugEnabled())log.debug("  Delegating to parent classloader1 " + parent);ClassLoader loader = parent;if (loader == null)loader = system;try {clazz = loader.loadClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Loading class from parent");if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {;}}//5、自行加载if (log.isDebugEnabled())log.debug("  Searching local repositories");try {clazz = findClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Loading class from local repository");if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {;}//如果自己也加载不了,那就只能让父加载器加载了if (!delegateLoad) {if (log.isDebugEnabled())log.debug("  Delegating to parent classloader at end: " + parent);ClassLoader loader = parent;if (loader == null)loader = system;try {clazz = loader.loadClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Loading class from parent");if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {;}}throw new ClassNotFoundException(name);}

内部逻辑:

  1. 先从 WebappClassLoader 的 ResourceEntry 缓存中查找
  2. 从 jvm 缓存中查找,比如去元数据区查找
  3. 利用系统类(应用类)加载器加载,避免 webapp 中的类覆盖掉标准类库中的类。
  4. 开启代理的话,则使用父加载器加载,这个默认没开启的。
  5. webappClassLoader 自行去加载
  6. 自己也没加载成功的话,最后只能让父加载器去加载

即:

  • 对于一些标准类库中的类,比如 Object 类,会让系统类加载器加载,然后一直委托到启动类加载器,这个过程是没有违背双亲委派的。
  • 而对于 webapp 中独有的类,则是 webappClassLoader 自行去加载,加载失败才让父加载器加载,明显是违背双亲委派的。

参考资料

  • 深度思考:老生常谈的双亲委派机制,JDBC、Tomcat 是怎么反其道而行之的?

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

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

相关文章

【校招VIP】TCP/IP模型之常用协议和端口

考点介绍&#xff1a; 大厂测试校招面试里经常会出现TCP/IP模型的考察&#xff0c;TCP/IP协议是网络基础知识&#xff0c;是互联网的基石&#xff0c;不管你是做开发、运维还是信息安全的&#xff0c;TCP/IP 协议都是你绕不过去的一环&#xff0c;程序员需要像学会看书写字一样…

java.lang.UnsupportedOperationException解决方法

java.lang.UnsupportedOperationException解决方法 先放错误信息业务场景报错分析先看报错代码位置进入源码查看至此 真相大白 解决方法总结 先放错误信息 业务场景 已知有学生 张三李四王五赵六 等人 private List<String> nameList Arrays.asList("张三", &…

Nginx前后端服务器部署

Nginx作为正反向代理的中转站&#xff0c;是连接前后端网络服务的媒介 Nginx下载&#xff1a;http://nginx.org/download/http://nginx.org/download/ 一、上传到服务器固定路径下并解压 上传到/opt/software/nginx-1.19.0.tar.gz cd /opt/software/ tar -zxvf nginx-1.19.0.…

Orchestrator自身高可用性方案

目录 获得 HA 的方法 一 没有高可用性 &#xff08;No high availability&#xff09; 使用场景 架构组成 架构图 二 半高可用性&#xff08;Semi HA&#xff09; 三 基于共享数据库后端高可用&#xff08;HA via shared backend&#xff09; 四 基于Raft协议高可用 五…

【Terraform学习】使用 Terraform 从 EC2 实例访问 S3 存储桶(Terraform-AWS最佳实战学习)

使用 Terraform 从 EC2 实例访问 S3 存储桶 实验步骤 前提条件 安装 Terraform&#xff1a; 地址 下载仓库代码模版 本实验代码位于 task_ec2_s3connet 文件夹中。 变量文件 variables.tf 在上面的代码中&#xff0c;您将声明&#xff0c;aws_access_key&#xff0c;aws_…

shell 11(shell重定向输入输出)

一、标准输入输出 标准输入介绍 从键盘读取用户输入的数据&#xff0c;然后再把数据拿到Shell程序中使用; 标准输出介绍 Shell程序产生的数据&#xff0c;这些数据一般都是呈现到显示器上供用户浏览查看

数字 IC 设计职位经典笔/面试题(三)

共100道经典笔试、面试题目&#xff08;文末可全领&#xff09; 1. IC 设计中同步复位与异步复位的区别&#xff1f; 同步复位在时钟沿变化时&#xff0c;完成复位动作。异步复位不管时钟&#xff0c;只要复位信号满足条件&#xff0c;就完成复位动作。异步复位对复位信号要求…

第1章:计算机网络体系结构

文章目录 1.1 计算机网络 概述1.概念2.组成3.功能4.分类5.性能指标1.2 计算机网络 体系结构&参考模型1.分层结构2.协议、接口、服务3.ISO/OSI模型:七层4.TCP/IP模型:四层1.1 计算机网络 概述 1.概念

Arduino 入门学习笔记12 读写外置EEPROM

Arduino 入门学习笔记12 读写外置EEPROM 一、准备工作1. 外置 EEPROM 简介2. Arduino操作外置 EEPROM 流程 二、读写操作流程1. 写操作流程2. 读操作 三、示例代码 一、准备工作 1. 外置 EEPROM 简介 外置I2C接口的EEPROM是一种常用的非易失性存储器&#xff0c;通过I2C总线与…

石油和天然气行业如何实现数字化转型和工业4.0

石油和天然气行业的数字化转型正面临着前所未有的挑战和机遇。尽管过去相对滞后&#xff0c;这个复杂而庞大的行业正逐渐意识到数字化的紧迫性&#xff0c;以应对市场变化、降低运营成本、提高效率和确保可持续性。然而&#xff0c;数字化转型的进程并非一帆风顺&#xff0c;行…

echarts 之 科技感进度条

1.图片展示 2.代码实现 /* ng qty 进度条 */ <template><div class"ngqty-progress"><div class"ngqty-info"><span>X4</span><span>50%</span></div><div :id"barNgQtyProgress index" c…

【校招VIP】产品行测考点之图的推理和分析

考点介绍&#xff1a; 大厂产品校招笔试里经常会出现行测的考察&#xff0c;而图的推理是行测里面稍微有难度的一部分。因为时间有限&#xff0c;很多同学因为没有解题思路而丢分。 『产品行测考点之图的推理和分析』相关题目及解析内容可点击文章末尾链接查看&#xff01; 一…

第 7 章 排序算法(4)(插入排序)

7.7插入排序 7.7.1插入排序法介绍: 插入式排序属于内部排序法&#xff0c;是对于欲排序的元素以插入的方式找寻该元素的适当位置&#xff0c;以达到排序的目的。 7.7.2插入排序法思想: 插入排序&#xff08;Insertion Sorting&#xff09;的基本思想是&#xff1a;把n个待排…

Kaggle分类问题Titanic——Machine Learning from Disaster

目录 前言1 题目介绍2 数据清洗3 数据可视化分析4 模型训练5 源码 前言 这是我在大三选修课的课程设计&#xff0c;内容参考了Kaggle上高赞的代码&#xff0c;有详细批注&#xff0c;整体比较基础&#xff0c;结构相对完整&#xff0c;便于初学者学习。这个是一个分类问题&am…

【C语言】动态内存管理(malloc,free,calloc,realloc)-- 详解

一、动态内存分配 定义&#xff1a;动态内存分配 (Dynamic Memory Allocation) 就是指在程序执行的过程中&#xff0c;动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样&#xff0c;需要预先分配存储空间&#xff0c;而是由系统根据程…

基于微信小程序+Springboot校园二手商城系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、目前专注于大学生项目实战开发,讲解,毕业答疑辅导✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3…

android Junit4编写自测用例

10多年的android开发经验&#xff0c;一直以来呢&#xff0c;也没有使用过android自带的测试代码编写。说来也惭愧。今天也花了点时间稍微研究了下。还挺简单。接下来就简单的说一下。 新建工程 直接默认新建一个工程&#xff0c;就会有两个目录androidTest和test(unitTest)两…

Qt+C++动力监控动画仿真SCADA上位机

程序示例精选 QtC动力监控动画仿真SCADA上位机 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<QtC动力监控动画仿真SCADA上位机>>编写代码&#xff0c;代码整洁&#xff0c;规则…

Spring 为什么使用三级缓存解决循环依赖

文章目录 前言1. 什么是循环依赖1.1 互相依赖1.2 递归依赖 2. Sping中循环依赖有什么问题&#xff1f;3. 什么是三级缓存4. Spring 可以解决哪些情况的循环依赖&#xff1f; 二级缓存作用——普通循环依赖实操环节1. 实例化类A对象2. 实例化类B对象3. B对象完成创建4.继续创建A…

合宙Air724UG LuatOS-Air LVGL API--对象

对象 概念 在 LVGL 中&#xff0c;用户界面的基本构建块是对象。例如&#xff0c;按钮&#xff0c;标签&#xff0c;图像&#xff0c;列表&#xff0c;图表或文本区域。 属性 基本属性 所有对象类型都共享一些基本属性&#xff1a; Position (位置) Size (尺寸) Parent (父母…