【JVM】(二)深入理解Java类加载机制与双亲委派模型

文章目录

  • 前言
  • 一、类加载过程
    • 1.1 加载(Loading)
    • 1.2 验证(Verification)
    • 1.3 准备(Preparation)
    • 1.4 解析(Resolution)
    • 1.5 初始化(Initialization)
  • 二、双亲委派模型
    • 2.1 类加载器
    • 2.2 什么是双亲委派模型
    • 2.3 双亲委派模型的解决的问题
    • 2.4 破坏双亲委派模型


前言

在 Java 中,类加载机制是 Java 虚拟机(JVM)的一个重要组成部分,它负责在运行时将 Java 类加载到内存中,并转换为可执行代码。理解类加载机制对于深入理解 Java 的运行机制和开发高质量的Java应用程序至关重要。

本文将深入探讨 Java 的类加载过程以及双亲委派模型。首先,我将详细介绍类加载过程的五个阶段。接下来,将重点介绍双亲委派模型以及它解决的问题。

一、类加载过程

1.1 加载(Loading)

加载是类加载的第一个阶段。在这个阶段,JVM 会根据类的全限定名(Fully Qualified Name)找到对应的字节码文件,并将其加载到内存中。加载阶段不仅仅包括从文件系统中读取字节码,还可能包括从网络、数据库等地方加载类的字节码。

详细说来,类的加载阶段需要完成三个任务:

  1. 获取字节流
    Java虚拟机根据类的全限定名(Fully Qualified Name)来获取定义该类的二进制字节流。这个过程可以通过从本地文件系统、网络、JAR包等位置读取类的字节码文件,并将其存储在内存中。

  2. 转化为运行时数据结构
    在获取类的字节流后,Java虚拟机将这个字节流所代表的静态存储结构(如类、字段、方法、常量池等)转化为方法区的运行时数据结构。这个过程包括解析字节码中的各种信息,并生成对应的运行时数据结构,用于在运行时执行类的各种操作。

  3. 生成java.lang.Class对象
    在内存中生成一个代表这个类的java.lang.Class对象。这个Class对象是在JVM中表示类的元数据信息的对象,通过这个对象可以访问类的方法、字段、构造函数等信息,以及执行类的各种操作。这个Class对象也是Java程序中获取类的入口,通过它可以访问类的各种静态和动态信息。

注意“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载 (Class Loading) 是不同的,一个是加载 (Loading)另一个是类加载 (Class Loading),不要把二者混淆。

1.2 验证(Verification)

验证是类加载过程的第二个阶段。在验证阶段,JVM 会对加载的字节码进行验证,确保字节码的结构是合法的、符合规范的,不包含安全漏洞和不符合 JVM 规范的内容。这个阶段是确保类加载过程的安全性和正确性的重要步骤

下图是 Java 虚拟机规范中的 Class 文件的结构定义,同时也是验证阶段所需要验证的:

1.3 准备(Preparation)

准备是类加载过程的第三个阶段。在准备阶段,JVM会为类的静态变量分配内存,并设置初始值(通常是零值)这里并不包括对静态变量赋值的操作,赋值的操作将在初始化阶段进行

例如有这样一段代码:

public static int value = 100;

在准备阶段,JVM 会给静态变量 value 分配内存,但是设置的初值是 0,而不是 100,因为赋值操作是在初始化阶段完成的。

1.4 解析(Resolution)

解析是类加载过程的第四个阶段。在解析阶段,JVM 会将符号引用替换为直接引用

  • 符号引用是一种在编译期产生的,用于描述类、字段、方法等的引用
  • 直接引用是指直接指向内存中的地址的引用

解析阶段的目的是将符号引用解析成直接引用,以便在之后的程序执行中更快速地定位到所引用的目标

1.5 初始化(Initialization)

初始化是类加载过程的最后一个阶段。在初始化阶段,JVM会执行类的初始化代码,包括对静态变量赋值执行静态初始化块。类的初始化是在首次使用类的时候触发的,只有在初始化完成后,类才算是被真正加载和准备好了。

二、双亲委派模型

2.1 类加载器

一提到 JVM 的类加载机制,不由自主的就会想到 双亲委派模型,而要理解这个模型,首先就需要了解类加载器。

在 JVM 中,默认有三种类加载器:

  1. 启动类加载器(Bootstrap Class Loader)

    • 这是JVM内部实现的特殊类加载器,由 C++ 编写,而不是 Java 类
    • 负责加载 JVM 自身需要的类,包括核心类库(如java.lang包中的类)等;
    • 启动类加载器是类加载器层次结构的最顶层,没有父加载器
    • 由于其是C++编写的,因此在Java代码中无法直接引用它。
  2. 扩展类加载器(Extension Class Loader)

  • 扩展类加载器是 Java 类,由sun.misc.Launcher$ExtClassLoader实现;
  • 负责加载 JRE(Java Runtime Environment)的扩展目录 lib/ext中的类
  • 扩展类加载器启动类加载器子加载器
  • 同时也是类加载器层次结构中的中间层。
  1. 应用程序类加载器(Application Class Loader)
  • 应用程序类加载器也是 Java 类,由sun.misc.Launcher$AppClassLoader实现;
  • 负责加载应用程序 classpath下的类,即我们自己编写的 Java 类
  • 应用程序类加载器扩展类加载器子加载器
  • 同时处于类加载器层次结构中的最底层。

以上三种类加载器构造了 JVM 的类加载器层次结构,即双亲委派模型。除了启动类加载器是由 C++ 语言实现外,其他的所有加载器均由 Java 实现,并且都继承了java.lang.ClassLoader

下图展示了 JVM 的类加载器的层次结构:

当然,除了默认的这三个类加载器外,开发人员还可以根据自己的需求实现自定义的类加载器。自定义类加载器需要继承自java.lang.ClassLoader,通过重写findClass方法来实现特定的类加载逻辑。自定义的类加载器常用于实现插件机制,热部署等功能。

2.2 什么是双亲委派模型

双亲委派模型是 Java 类加载器的一种类加载机制,简单来说,核心思想就是:一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。

双亲委派模型的类加载过程大致如下图所示:

  • 当一个类加载器收到加载请求的时候(子加载器),它首先会把这个加载请求委派给其父加载器,父加载器可能会继续委托给其父加载器,依次递归。
  • 到达类加载器层次结构的最顶层(启动类加载器)后,再尝试进行类加载。
  • 如果当前加载器无法加载时,就会将加载任务交给其子加载器,由子加载器尝试进行类加载

2.3 双亲委派模型的解决的问题

双亲委派模型主要解决了两个问题:

  1. 类的隔离和防止冲突
  • 在复杂的 Java 应用程序中,可能会涉及许多不同的类库和模块。这些类库和模块可能引用了相同的类名,如果不加以限制,可能就会导致类名冲突
  • 双亲委派模型通过层级结构的类加载器,确保每个类加载器都将加载任务优先委派给父加载器进行加载,这样同名的类只会被加载一次,并且加载过程是有序的,避免了类的冲突和混淆

举个例子:

  • 假设应用程序的类加载器需要加载 java.lang.String 类。
  • 它首先会委派给扩展类加载器,扩展类加载器也找不到,再委派给启动类加载器。因为启动类加载器能够找到并加载 java.lang.String 类,所以它会将该类返回给应用程序类加载器。
  • 由于类加载器的委派顺序,即便应用程序中有自定义java.lang.String 类,也不会被加载,从而保证了类的隔离和防止冲突。
  1. 安全性和可信任代码的执行

Java中的核心类库位于JVM内部,它们提供了Java编程语言的基本功能。这些核心类库在 JVM 启动时由启动类加载器加载。通过双亲委派模型,可以确保核心类库只会被启动类加载器加载,而不会被应用程序类加载器或其他自定义类加载器加载

这种安排提高了Java程序的安全性,因为核心类库的来源可信,不会被恶意类替代。如果允许应用程序类加载器直接加载核心类库,那么恶意类可能会替代核心类库中的某些类,从而导致安全漏洞。双亲委派模型通过限制核心类库的加载,确保了可信任代码的执行,并防止恶意类的篡改

2.4 破坏双亲委派模型

尽管双亲委派模型在大多数情况下是有益的,但是有些特定的场景下就需要破坏它,其中 JDBC 就是一个典型的例子

  • 在 JDBC 中,数据库厂商提供的自己的 JDBC 驱动,这些驱动实现了 JDBC 标准接口的类库。由于 JDBC 驱动需要和特定的数据库交互,因此它们通常由数据库厂商提供,而不是 Java 标准的一部分。而数据库厂商非常多,因此 JDBC 驱动的种类也就非常多了。

  • 考虑到这种情况,JDBC 驱动就需要被应用程序自己加载,而不是委托给父类。如果此时还是按照双亲委派模型的规则进行类加载,那么加载的就是 Java 提供的 JDBC 标准接口的类库中的类了,而不是特定数据库的类。

  • 为了解决这个问题,JDBC 驱动的加载通常是通过反射来实现的应用程序类加载器可以直接加载驱动的类,而不通过双亲委派模型。这样应用程序可以加载自己所需的特定驱动类,而不受父类加载器的限制

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【go-zero】docker镜像直接部署API与RPC服务 如何实现注册发现?docker network 实现 go-zero 注册发现

一、场景&问题 使用docker直接部署go-zero微服务会发现API无法找到RPC服务 1、API无法发现RPC服务 用docker直接部署 我们会发现API无法注册发现RPC服务 原因是我们缺少了docker的network网桥 2、系统内查看 RPC服务运行正常API服务启动,通过docker logs 查看日志还是未…

寄存器详解(一)

目录 前言: 通用寄存器 示例: 通用寄存器的划分 汇编指令 cpu物理地址的形成 地址加法器运算示例: 1. 相关部件提供段地址和偏移地址 2. 段地址和偏移地址送入地址加法器 3. 段地址*16 4. 求出物理地址 5. 输出物理地址 段的概念 Deb…

在线五子棋对战

目录 数据管理模块(数据库设计) 前端界面模块 业务处理模块 会话管理模块网络通信模块(session,cookie) 在线管理模块 房间管理模块 用户匹配模块 项目扩展 数据管理模块(数据库设计) 数据库中有可能存在很多张表&#xf…

MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图

MQTT 概述 MQTT (Message Queue Telemetry Transport) 是一个轻量级传输协议,它被设计用于轻量级的发布/订阅式消息传输,MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化。是一种简单、稳定、开放、轻量级易于实现的消息…

APP开发中的性能优化:提升用户满意度的关键

APP开发中的性能优化是需要持续进行的,它不仅能够让用户体验到 APP的使用感受,还能在一定程度上提升用户的满意度,从而提升 APP的粘性和转化率。不过在实际开发中,很多 APP开发公司会存在性能优化上的问题,这就需要了解…

[C++项目] Boost文档 站内搜索引擎(3): 建立文档及其关键字的正排 倒排索引、jieba库的安装与使用...

之前的两篇文章: 第一篇文章介绍了本项目的背景, 获取了Boost库文档 🫦[C项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍…第二篇文章 分析实现了parser模块. 此模块的作用是 对所有文档html文件, 进行清理并汇总 🫦[C项目] …

【力扣每日一题】2023.8.4 不同路径3

目录 题目: 示例: 分析: 代码: 题目: 示例: 分析: 在二维网格之上,让我们模拟从开头走到末尾,并且要经过所有能经过的点,问我们有多少种走法。 看到这道…

c++学习(异常)[28]

c语言处理错误机制 c异常概念 try {//保护的标识代码 }catch(ExceptionName e1) {//catch块 }catch(ExceptionName e2) {//catch块 }catch(ExceptionName eN) {//catch块 }匹配 优先调用链中最近的捕获 异常若不被捕获则报错终止程序 try { }catch ( ... ) //可以捕获任意类…

TCP的三次握手和四次挥手······详解

1、三次握手 三次握手是建立连接的过程 如图大致为三次握手的流程图: 当客户端对服务端发起连接时,会先发一个包连接请求数据,去询问能否建立连接,该数据包称为 “SYN”包 然后,如果对方同意连接,那么…

RabbitMQ:概念和安装,简单模式,工作,发布确认,交换机,死信队列,延迟队列,发布确认高级,其它知识,集群

1. 消息队列 1.0 课程介绍 1.1.MQ 的相关概念 1.1.1.什么是MQ MQ(message queue:消息队列),从字面意思上看,本质是个队列,FIFO 先入先出,只不过队列中存放的内容是message 而已,还是一种跨进程的通信机制…

2024年杭州电子科技大学MEM项目招生信息全面了解

2024年全国管理类硕士联考备考已经到了最火热的阶段,不少考生开始持续将注意力集中在备考的规划中!杭州达立易考教育整合浙江省内的MEM目信息,为大家详细梳理了相关报考参考内容,方便大家更好完成择校以及针对性的备考工作。本期为…

史上最全docker启动命令

docker Docker 启动镜像 一、查看当前docker中下载的镜像,如下图,当前我的Docker容器中存在两个镜像 ,tomcat、mysql 二、启动镜像 (因启动命令参数过多,同时各种镜像启动时可以增加额外的参数,本次以启动mysql5.6为例…

(12)理解委托,反射,Type,EvenInfo,插件, 组合枚举,BindingFlags,扩展方法及重载,XML认识

一、复习委托事件 1、委托复习。 private delegate int MyDelegate(int a, int b); //1.定义委托类型private static void Main(string[] args){MyDelegate md new MyDelegate(AddDelegate);//2.声明委托变量int result md(1, 2);//3.调用委托Console.WriteLine(result);Cons…

Vue中默认插槽,具名插槽,作用域插槽区别详解

默认插槽&#xff1a; App.vue : 在app.vue中使用MyCategory&#xff0c;里面包裹的结构是不显示的&#xff0c;要想在页面中显示&#xff0c;就需要用到插槽。在子组件MyCategory中定义 <template><div class"container"><MyCategory title"美…

【Opencv入门到项目实战】(二):图像阈值与平滑处理

文章目录 1.图像阈值处理1.1简单阈值处理&#xff08;Simple Thresholding&#xff09;1.2自适应阈值处理&#xff08;Adaptive Thresholding&#xff09;1.3Otsus阈值处理 2.平滑处理1.1均值滤波&#xff08;Mean Filter&#xff09;1.2高斯滤波&#xff08;Gaussian Filter&a…

程序员自由创业周记#5:加一上线

程序员自由创业周记#5&#xff1a;加一上线 这是一位程序员进行独立开发创业的记录&#xff0c;将分享创业过程中的所思所想以及收支明细。 充实 如果说程序员独立创业的成功率只有5%&#xff0c;那如果家里有一位3岁多还没上幼儿园的小朋友要照顾&#xff0c;成功的概率至少还…

rv1109/1126 rknn 模型部署过程

rv1109/1126是瑞芯微出的嵌入式AI芯片&#xff0c;带有npu, 可以用于嵌入式人工智能应用。算法工程师训练出的算法要部署到芯片上&#xff0c;需要经过模型转换和量化&#xff0c;下面记录一下整个过程。 量化环境 模型量化需要安装rk的工具包&#xff1a; rockchip-linux/rk…

【Spring】(一)Spring设计核心思想

文章目录 一、初识 Spring1.1 什么是 Spring1.2 什么是 容器1.3 什么是 IoC 二、对 IoC 的深入理解2.1 传统程序开发方式存在的问题2.2 控制反转式程序的开发2.3 对比总结 三、对 Spring IoC 的理解四、DI 的概念4.1 什么是 DI4.2 DI 与 IoC的关系 一、初识 Spring 1.1 什么是…

flutter:Future、Stream、RxDart

Future 在Flutter中&#xff0c;Future是Dart语言中的一个类&#xff0c;用于表示异步操作的结果。与Future相关的的重要关键字包括async和await。 async&#xff1a;这个关键字用于在方法或函数声明前添加&#xff0c;以指示该方法为异步方法。在异步方法中&#xff0c;执行…

c++画出分割图像,水平线和垂直线

1、pca 找到图像某个区域的垂直线&#xff0c;并画出来 // 1、 斑块的框 血管二值化图&#xff0c;pca 找到垂直血管壁的直线, 还是根据斑块找主轴方向吧// Step 1: 提取斑块左右范围内的血管像素点坐标&#xff0c;std::vector<cv::Point> points;for (int y 0; y <…