【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)找到对应的字节码文件,并将其加载到内存中。加载阶段不仅仅包括从文件系统中读取字节码,还可能包括从网络、数据库等地方加载类的字节码。
详细说来,类的加载阶段需要完成三个任务:
-
获取字节流:
Java虚拟机根据类的全限定名(Fully Qualified Name)来获取定义该类的二进制字节流。这个过程可以通过从本地文件系统、网络、JAR包等位置读取类的字节码文件,并将其存储在内存中。 -
转化为运行时数据结构:
在获取类的字节流后,Java虚拟机将这个字节流所代表的静态存储结构(如类、字段、方法、常量池等)转化为方法区的运行时数据结构。这个过程包括解析字节码中的各种信息,并生成对应的运行时数据结构,用于在运行时执行类的各种操作。 -
生成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 中,默认有三种类加载器:
-
启动类加载器(Bootstrap Class Loader)
- 这是JVM内部实现的特殊类加载器,由 C++ 编写,而不是 Java 类;
- 它负责加载 JVM 自身需要的类,包括核心类库(如
java.lang
包中的类)等; - 启动类加载器是类加载器层次结构的最顶层,没有父加载器;
- 由于其是C++编写的,因此在Java代码中无法直接引用它。
-
扩展类加载器(Extension Class Loader)
- 扩展类加载器是 Java 类,由
sun.misc.Launcher$ExtClassLoader
实现; - 它负责加载 JRE(Java Runtime Environment)的扩展目录
lib/ext
中的类; 扩展类加载器
是启动类加载器
的子加载器
;- 同时也是类加载器层次结构中的中间层。
应用程序类加载器(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 双亲委派模型的解决的问题
双亲委派模型主要解决了两个问题:
- 类的隔离和防止冲突
- 在复杂的 Java 应用程序中,可能会涉及许多不同的类库和模块。这些类库和模块可能引用了相同的类名,如果不加以限制,可能就会导致类名冲突。
- 双亲委派模型通过层级结构的类加载器,确保每个类加载器都将加载任务优先委派给父加载器进行加载,这样同名的类只会被加载一次,并且加载过程是有序的,避免了类的冲突和混淆。
举个例子:
- 假设应用程序的类加载器需要加载
java.lang.String
类。- 它首先会委派给扩展类加载器,扩展类加载器也找不到,再委派给启动类加载器。因为启动类加载器能够找到并加载
java.lang.String
类,所以它会将该类返回给应用程序类加载器。- 由于类加载器的委派顺序,即便应用程序中有
自定义
的java.lang.String
类,也不会被加载,从而保证了类的隔离和防止冲突。
- 安全性和可信任代码的执行
Java中的核心类库位于JVM内部,它们提供了Java编程语言的基本功能。这些核心类库在 JVM 启动时由启动类加载器加载。通过双亲委派模型,可以确保核心类库只会被启动类加载器加载,而不会被应用程序类加载器或其他自定义类加载器加载
。
这种安排提高了Java程序的安全性,因为核心类库的来源可信,不会被恶意类替代
。如果允许应用程序类加载器直接加载核心类库,那么恶意类可能会替代核心类库中的某些类,从而导致安全漏洞。双亲委派模型通过限制核心类库的加载,确保了可信任代码的执行,并防止恶意类的篡改。
2.4 破坏双亲委派模型
尽管双亲委派模型在大多数情况下是有益的,但是有些特定的场景下就需要破坏它,其中 JDBC
就是一个典型的例子:
-
在 JDBC 中,数据库厂商提供的自己的 JDBC 驱动,这些驱动
实现了 JDBC 标准接口的类库
。由于 JDBC 驱动需要和特定的数据库交互,因此它们通常由数据库厂商提供,而不是 Java 标准的一部分。而数据库厂商非常多,因此 JDBC 驱动的种类也就非常多了。 -
考虑到这种情况,JDBC 驱动就需要被应用程序自己加载,而不是委托给父类。如果此时还是按照
双亲委派模型
的规则进行类加载,那么加载的就是 Java 提供的JDBC 标准接口的类库
中的类了,而不是特定数据库的类。 -
为了解决这个问题,JDBC 驱动的加载通常是通过反射来实现的,应用程序类加载器可以直接加载驱动的类,而不通过双亲委派模型。这样应用程序可以加载自己所需的特定驱动类,而不受父类加载器的限制。
相关文章:

【JVM】(二)深入理解Java类加载机制与双亲委派模型
文章目录 前言一、类加载过程1.1 加载(Loading)1.2 验证(Verification)1.3 准备(Preparation)1.4 解析(Resolution)1.5 初始化(Initialization) 二、双亲委派…...

npm i 报错项目启动不了解决方法
1.场景 在另一台电脑低版本node环境跑的react项目,换到另一台电脑node18环境执行npm i时候报错 2.解决方法 脚本前加上set NODE_OPTIONS--openssl-legacy-provider...

【从零开始学习JAVA | 第三十七篇】初识多线程
目录 前言: 编辑 引入: 多线程: 什么是多线程: 多线程的意义: 多线程的应用场景: 总结: 前言: 本章节我们将开始学习多线程,多线程是一个很重要的知识点ÿ…...

微信新功能,你都知道吗?
近日iOS 微信8.0.40正式版来了,一起来看看有哪些变化? 1、朋友圈置顶 几个月前微信开始内测「朋友圈置顶」功能,从网友们的反馈来看,iOS 微信 8.0.40 似乎扩大了内测范围,更多用户可以体验到该功能了。 大家可以去自己…...

Android 中 app freezer 原理详解(二):S 版本
基于版本:Android S 0. 前言 在之前的两篇博文《Android 中app内存回收优化(一)》和 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理,为什么叫这个名字,而不…...

Vue3_04_ref 函数和 reactive 函数
ref 函数 声明变量时,赋值的值要写在 ref() 函数中修改变量时,变量名.value xxx在模板中使用时可以省略掉 .value,直接使用变量名即可 <template><h1>一个人的信息</h1><h2>姓名:{{name}}</h2><…...

05 Ubuntu下安装.deb安装包方式安装vscode,snap安装Jetbrains产品等常用软件
使用deb包安装类型 deb包指的其实就是debian系统,ubuntu系统是基于debian系统的发行版。 一般我们会到需要的软件官网下载deb安装包,然后你既可以采用使用“软件安装”打开的方法来进行安装,也可以使用命令行进行安装。我推荐后者ÿ…...

性能测试jmeter连接数据库jdbc(sql server举例)
一、下载第三方工具包驱动数据库 1. 因为JMeter本身没有提供链接数据库的功能,所以我们需要借助第三方的工具包来实现。 (有这个jar包之后,jmeter可以发起jdbc请求,没有这个jar包,也有jdbc取样器,但不能发起…...

8.3 C高级 Shell脚本
写一个脚本,包含以下内容: 显示/etc/group文件中第五行的内容创建目录/home/ubuntu/copy切换工作路径到此目录赋值/etc/shadow到此目录,并重命名为test将当前目录中test的所属用户改为root将test中其他用户的权限改为没有任何权限 #!/bin/b…...

2023年华数杯A题
A 题 隔热材料的结构优化控制研究 新型隔热材料 A 具有优良的隔热特性,在航天、军工、石化、建筑、交通等 高科技领域中有着广泛的应用。 目前,由单根隔热材料 A 纤维编织成的织物,其热导率可以直接测出;但是 单根隔热材料 A 纤维…...

【零基础学Rust | 基础系列 | 函数,语句和表达式】函数的定义,使用和特性
文章标题 简介一,函数1,函数的定义2,函数的调用3,函数的参数4,函数的返回值 二,语句和表达式1,语句2,表达式 总结: 简介 在Rust编程中,函数,语句…...
加解密算法+压缩工具
sha256 工具类 package com.fanghui.vota.packages.util;import org.slf4j.Logger; import org.slf4j.LoggerFactory;import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.math.BigInteger…...

FeignClient接口的几种方式总结
FeignClient这个注解,已经封装了远程调用协议。在springboot的开发,或者微服务的开发过程中,我们需要跨服务调用,或者调用外部的接口,我们都可以使用FeignClient。 一、FeignClient介绍 FeignClient 注解是 Spring Cl…...

springBoot多数据源使用tdengine(3.0.7.1)+MySQL+mybatisPlus+druid连接池
一、安装部署 1、我这里使用的 3.0.7.1版本,因为我看3.x版本已经发布了一年了,增加了很多新的功能,而且3.x官方推荐,对于2.x的版本,官网都已经推荐进行升级到3.x,所以考虑到项目以后的发展,决定…...

剑指Offer 05.替换空格
剑指Offer 05.替换空格 目录 剑指Offer 05.替换空格05.替换空格题目代码(容易想到的)利用库函数的方法题解(时间复杂度更低)面试:为什么java中String类型是不可变的 05.替换空格 题目 官网题目地址 代码(…...
ChatGPT的功能与特点
随着人工智能技术的不断发展,ChatGPT作为OpenAI公司开发的基于GPT-3.5架构的大型语言模型,正引领着智能交互的新纪元。ChatGPT的功能与特点使其能够在多个领域展现出惊人的能力,本文将深入探讨ChatGPT的功能与特点,以及它在人工智…...

Vue2.0基础
1、概述 Vue(读音/vju/,类似于view)是一套用于构建用户界面的渐进式框架,发布于2014年2月。与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层(也就是可以理解为HTMLCSSJS)ÿ…...
rust 如何定义[u8]数组?
在Rust中,有多种方式可以定义 [u8] 数组。以下是一些常见的方式: 使用数组字面量初始化数组: let array: [u8; 5] [1, 2, 3, 4, 5];使用 vec! 宏创建可变长度的数组: let mut vec: Vec<u8> vec![1, 2, 3, 4, 5];使用 v…...
关于Hive的使用技巧
前言 Hive是一个基于Hadoop的数据仓库基础架构,它提供了一种类SQL的查询语言,称为HiveQL,用于分析和处理大规模的结构化数据。 Hive的主要特点包括: 可扩展性:Hive可以处理大规模的数据,支持高性能的并行…...

【C++】BSTree 模拟笔记
文章目录 概念插入和删除非递归实现中的问题递归中的引用简化相关OJ复习直达 概念 由下面二叉搜索树的性质可以知道,中序遍历它便可以得到一个升序序列,查找效率高,小于往左找,大于往右走。最多查找高度次,走到到空&am…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...