JVM专题——类文件加载

本文部分内容节选自Java Guide和《深入理解Java虚拟机》, Java Guide地址: https://javaguide.cn/java/jvm/class-loading-process.html

🚀 基础(上) → 🚀 基础(中) → 🚀基础(下) → 🤩集合(上) → 🤩集合(下) → 🤗JVM专题1 → 🤗JVM专题2

类加载过程

一个类从被加载到 JVM 内存开始, 到卸载出内存位置, 它的整个生命周期会经历 加载 , 验证 , 准备 , 解析 , 初始化 , 使用卸载 七个阶段, 其中, 验证 , 准备 , 解析 这三个阶段统称为 连接

加载, 验证, 准备, 初始化, 卸载这五个阶段的顺序是确定的, 类型的加载过程必须按照这种顺序按部就班地开始, 而解析顺序不一定

加载

类加载过程的第一步, 主要完成下面三件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 java.lang.Class 对象, 作为方法区这个类的各种数据的访问入口

加载这一步主要是通过 类加载器 完成的, 具体是由哪个类加载器加载由 双亲委派模型 决定

每个 Java 类都有一个引用指向加载它的 ClassLoader , 不过, 数组类不是由 ClassLoader 加载的, 而是 JVM 在需要的时候自动创建的, 数组类通过 getClassLoader() 方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的

一个非数组类的加载阶段 (加载阶段获取类的二进制字节流的动作) 是可控性最强的阶段, 这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式 (重写一个类加载器的 loadClass() 方法)

加载阶段与连接阶段的部分动作 (如一部分字节码文件格式验证动作) 是交叉进行的, 加载阶段尚未完成, 连接阶段可能已经开始, 但这些夹在加载阶段之中进行的动作, 仍然属于连接阶段的一部分, 这两个阶段的开始时间仍然保持着固定的先后顺序

验证

验证是连接阶段的第一步, 这一阶段的目的是为了确保 Class 文件的字节流中的信息符合 Java 虚拟机规范的全部约束要求, 保证这些信息被当作代码运行后不会危害虚拟机的安全

Java 虚拟机如果不检查输入的字节流, 对其完全信任的话, 很可能会因为载入了有错误或者有恶意企图的字节码流导致整个系统受攻击甚至崩溃, 所以验证字节码是 Java 虚拟机保护自身的必要措施

验证阶段大致上会完成下面四个阶段的检验动作: 文件格式验证, 元数据验证, 字节码验证和符号引用验证

文件格式验证

验证点如下:

  1. 是否以魔数 0xCAFEBABE 开头
  2. 主, 次版本号是否在当前 Java 虚拟机的可接受范围之内
  3. 常量池中是否有不被支持的常量类型
  4. 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
  5. CONSTANT_Utf8_info型的常量中是否有不符合 UTF-8 编码的数据
  6. Class 文件中各个部分及文件本身是否有被删除的或附加的其他信息

主要目的是保证输入的字节流能够正确地解析并存储于方法区内, 格式上符合描述一个 Java 类型信息的要求.

这阶段的验证是基于二进制字节流进行的, 只有通过了这个阶段的验证之后, 这段字节流才被允许进入 Java 虚拟机内存的方法区中进行存储, 所以后面的三个验证阶段全部是基于方法区的存储结构上进行的, 不会再读取, 操作字节流了

元数据验证

验证点如下:

  1. 这个类是否有父类 (除了 java.lang.Object 之外, 所有的类都应当有父类)
  2. 这个类的父类是否继承了不允许被继承的类
  3. 如果这个类不是抽象类, 是否实现了其父类或接口之中要求实现的所有方法
  4. 类中的字段, 方法是否与父类产生矛盾

主要是对字节码描述的信息进行语义分析, 保证不存在与 Java语言规范 定义相悖的元数据信息

字节码验证

验证点如下:

  1. 保证任意时刻操作数栈的数据类型和指令代码序列都能配合工作, 例如不能出现类似于 “在操作栈中放置了一个int类型的数据, 使用时却按照long类型加载入本地变量表中” 这种情况
  2. 保证任何跳转指令都不会跳转到方法体之外的字节码指令上
  3. 保证方法体中的类型转换总是有效的, 例如可以把一个子类对象赋给父类数据类型, 这是安全的, 但是把父类对象赋值给子类数据类型甚至一个毫无继承关系的数据类型, 则是不合法的

符号引用验证

验证点如下:

  1. 符号引用中通过字符串描述的全限定名是否能找到对应的类
  2. 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段
  3. 符号引用中类, 字段, 方法的可访问性是否可被当前类访问

符号引用验证的目的是确保解析行为可以正常执行, 如果无法通过符号引用验证, Java 虚拟机会抛出一个 java.lang.IncompatibleClassChangeError 的子类异常, 典型的有: java.lang.IllegalAccessError , java.lang.NoSuchFieldError , java.lang.NoSuchMethodError

准备

准备阶段是正式为类中定义的变量 (即静态变量, 被static修饰的变量) 分配内存并设置类变量初始值的阶段.

需要注意的几点

  1. 这时候进行内存分配的仅包括类变量, 而不包括实例变量, 实例变量会在对象实例化时随着对象一起分配在 Java 堆中
  2. 这里所说的初始值 “通常情况下” 是数据类型的零值(如 0, 0L, null, false 等)
  3. 类变量使用的内存都应当在方法区中分配, 不过需要注意的是, 在 JDK7 以前, HotSpot 使用永久代实现方法区时, 是符合这种逻辑概念的, 但是在 JDK7 之后, HotSpot已经把原本放在永久代的字符串常量池, 静态变量等移动到堆中, 这时候类变量则会随着 Class 对象一起存放在 Java 堆中

解析

解析阶段是 JVM 将常量池中的符号引用直接替换为直接引用的过程

解析动作主要针对类或接口, 字段, 类方法, 接口方法, 方法类型, 方法句柄和调用点限定符这7种符号引用进行

初始化

初始化阶段是执行初始化方法 <clinit>() 方法的过程, 是类加载的最后一步, 这一步 JVM 才开始真正执行类中定义的 Java 程序代码

对于 <clinit>() 方法的调用, 虚拟机会自己确保其在多线程环境中的安全性, 因为 <clinit>() 方法是带锁线程安全, 所以在多线程环境下进行类初始化的话可能会引起多个线程阻塞, 并且这种阻塞很难被发现

对于初始化阶段, 虚拟机严格规范了有且只有6种情况, 必须对类进行初始化

  1. 当遇到 new , getstatic , putstaticinvokestatic 这4条字节码指令时, 比如 new 一个类, 读取一个静态字段, 或调用一个类的静态方法
    • 当 JVM 执行 new 指令时会初始化类, 即当程序创建一个类的实例对象
    • 当 JVM 执行 getstatic 指令时会初始化类, 即程序访问类的静态变量
    • 当 JVM 执行 putstatic 指令时会初始化类, 即程序给类的静态变量赋值
    • 当 JVM 执行 invokestatic 指令时会初始化类, 即程序调用类的静态方法
  2. 使用 java.lang.reflect 包的方法对类进行反射调用时如 Class.forName("...") , newInstance() 等. 如果类没有初始化, 需要触发其初始化
  3. 初始化一个类, 如果其父类没有初始化, 则先触发父类的初始化
  4. 当虚拟机启动时, 用户需要定义一个要执行的主类, 虚拟机会先初始化这个类
  5. MethodHandleVarhandle 可以看作是轻量级的反射调用机制, 而要想使用这两个调用, 就必须先使用 findStaticVarHandle 来初始化要调用的类
  6. 当一个接口中定义了 JDK8 新加入的默认方法时, 如果有这个接口的实现类发生了初始化, 那该接口要在其之前被初始化

类卸载

卸载类即该类的 Class 对象被GC

卸载类需要满足 3 个要求:

  1. 该类的所有实例对象都已被 GC, 也就是说堆不存在该类的实例对象
  2. 该类没有在其他地方被引用
  3. 该类的类加载器实例已被GC

类加载器

类与类加载器

类加载器的主要作用就是加载 Java 类的字节码 (.class 文件) 到 JVM 中 (在内存中生成一个代表该类的 Class 对象)

对于任意一个类, 都必须由加载它的类加载器和这个类本身一起共同确定其在 JVM 中的唯一性, 每个类加载器都有一个独立的类名称空间

也就是说, 比较两个类是否 “相等”, 只有在这两个类是由同一个类加载器加载的前提下才有意义, 否则, 即使这两个类来源于同一个 Class 文件, 被同一个 JVM 加载, 只要加载它们的类加载器不同, 那么这两个类必定不相等

此处的相等, 包括 Class 对象的 equals() 方法, isAssignableFrom() 方法, isInstance 方法的返回结果, 也包括了使用 instanceof 关键字做对象所属关系判断等各种情况

加载规则

JVM 启动时, 并非会一次性加载所有的类, 而是根据需要动态加载类. 也就是说大部分类都是在具体用到的时候才会去加载, 这样对内存更友好

对于已经加载的类会放在 ClassLoader 中, 在类加载时, 系统会先判断这个类是否被加载过, 已经被加载过的类会直接返回, 否则会尝试加载. 也就是说, 对于一个类加载器来说, 相同二进制名称的类只会被加载一次

类加载器总结

JVM 中内置了3个重要的 ClassLoader

  1. BootstrapClassLoader (启动类加载器): 最顶层加载类, 由C++实现, 通常表示为null, 且没有父级, 主要用来加载 JDK 内部的核心类库以及被 -Xbootclasspath 参数指定的路径下的所有类
  2. ExtensionClassLoader (扩展类加载器): 主要负责加载 %JRE_HOME%/lib/ext 目录下的jar包和类以及被 java.ext.dirs 系统变量所指定的路径下所有的类
  3. AppClassLoader (应用程序类): 负责加载当前应用 classpath 下所有的jar包和类

除了上面三个类加载器, 用户还可以自定义类加载器

除了 BootstrapClassLoader 是 JVM 自身的一部分之外, 其他所有的类加载器都是在 JVM 外部实现的, 并且全部继承自 ClassLoader 抽象类. 这样的好处是用户可以自定义类加载器, 以便让应用程序自己决定如何去获取所需的类

每个 ClassLoader 都可以通过 getParent() 获取其父加载器, 如果获取到的加载器为 null 的话, 说明该类是通过 BootstrapClassLoader 加载到

自定义类加载器

如果需要自定义类加载器, 需要继承 ClassLoader 抽象类

ClassLoader 中有两个关键的方法

  • protected Class loadClass(String name, boolean resolve) : 加载指定二进制名称的类, 实现双亲委派机制
  • protected Class findClass(String name) : 根据类的二进制名称查找类, 默认实现是空方法

如果不想打破双亲委派机制, 就重写 ClassLoader 中的 findClass() 方法. 但是, 如果想打破双亲委派机制就需要重写 loadClass() 方法

双亲委派模型

在这里插入图片描述

如图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”. 双亲委派模型要求除了顶层的启动类加载器之外, 其他类加载器都必须有自己的父类加载器. 这里类加载器之间的父子关系不是以继承的关系来实现的, 而是通常使用组合关系复用父加载器的代码

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

  • 在类加载时候, 系统会判断这个类是否被加载过, 已经被加载过的类会直接返回, 否则会尝试加载
  • 类加载器在进行类加载的时候, 他首先不会自己尝试加载这个类, 而是把这个请求委派给父类加载器去完成. 这样的话, 所有的请求都会传送到顶层的启动类加载器 BootstrapClassLoader
  • 只有当父加载器反馈自己无法完成这个加载请求, 子加载器才会尝试自己去加载
  • 如果子加载器也无法加载, 抛出 ClassNotFoundException 异常

双亲委派模型保证了 Java 程序的稳定运行, 也避免类的重复加载, 也保证了 Java 的核心 API 不被篡改

打破双亲委派模型

双亲委派模型并非是一个具有强制性约束的模型, 而是 Java 设计者推荐给开发者的类加载器实现方式

为了打破双亲委派模型, 需要继承 ClassLoader , 如果不想打破双亲委派模型, 就重写 ClassLoader 中的 findClass() 方法, 如果要打破双亲加载机制就需要重写 loadClass() 方法

重写 loadClass()方法之后, 我们就可以改变传统双亲委派模型的执行流程.例如, 子类加载器可以在委派给父类加载器之前, 先自己尝试加载这个类, 或者在父类加载器返回之后, 再尝试从其他地方加载这个类. 具体的规则由我们自己实现,根据项目需求定制化

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

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

相关文章

(免费分享)基于springboot,vue房屋租赁管理系统

功能说明&#xff1a; * 普通用户角色&#xff1a; 1. 寻找房源功能--提供了两种寻找房源的功能&#xff0c;一种是普通用户在平台上搜索、筛选主动寻找房源的功能&#xff0c;另一种是用户填写征集房源的条件&#xff0c;系统会持续将最新符合条件的房源推送给用户。 2. …

Vuex的模块化管理

1&#xff1a;定义一个单独的模块。由于mutation的第二个参数只能提交一个对象&#xff0c;所以这里的ThisLog是个json串。 2&#xff1a;在Vuex中的index.js中引入该模块 3&#xff1a;在别的组件中通过...mapState调用模块保存的State的值。 4&#xff1a;用...mapMutations修…

【番外篇2】统计学-方差分析

方差分析 方差分析&#xff08;ANOVA&#xff09;是一种用于比较三个或三个以上组之间平均值是否有显著差异的统计方法。通俗地说&#xff0c;就是用来确定不同组之间的平均值是否有显著差异。 让我们通过一个简单的例子来解释方差分析&#xff1a; 假设你是一位教育工作者&a…

界面控件Kendo UI for jQuery 2024 Q1亮点 - 新的ToggleButton组件

Telerik & Kendo UI 2024 Q1 版本于2024年初发布&#xff0c;在此版本中将AI集成到了UI组件中&#xff0c;在整个产品组合中引入AI Prompt组件以及10多个新的UI控件、支持Angular 17、多个数据可视化功能增强等。 P.S&#xff1a;Kendo UI for jQuery提供了在短时间内构建…

哲♂学家带你用顺序表实现通讯录

实现通讯录能使我们进一步加深对顺序表的理解&#xff0c;接下来就由本哲♂学家带你手把手实现通信录。 其中需要用到顺序表的知识可以点击下面链接了解&#xff1a;http://t.csdnimg.cn/9SjGd话不多说&#xff0c;我们♂开始吧。 一、通讯录头文件声明 由于我们前面已经写过…

四核8g服务器价格多少钱?

2024年腾讯云4核8G服务器租用优惠价格&#xff1a;轻量应用服务器4核8G12M带宽646元15个月&#xff0c;CVM云服务器S5实例优惠价格1437.24元买一年送3个月&#xff0c;腾讯云4核8G服务器活动页面 txybk.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云4核8G服务器优惠价格 轻…

【数据结构】——二叉树的递归实现,看完不再害怕递归

创作不易&#xff0c;感谢三连加支持&#xff1f;&#xff01; 一 递归理解 递归无非就是相信它&#xff0c;只有你相信它&#xff0c;你才能写好递归&#xff01;为什么&#xff1f;请往下看 在进入二叉树的实现之前&#xff0c;我们得先理解一遍递归&#xff0c;可能很多…

Android JNI 调用第三方SO

最近一个项目使用了Go 编译了一个so库&#xff0c;但是这个so里面还需要使用第三方so库pdfium, 首先在Android工程把2个so库都放好 在jni中只能使用dlopen方式&#xff0c;其他的使用函数指针的方式来调用&#xff0c;和windows dll类似&#xff0c;不然虽然编译过了但是会崩溃…

基于H2O AutoML与集成学习策略的房屋售价预测模型研究与实现

项目简述&#xff1a; 本项目采用H2O AutoML工具&#xff0c;针对加州房屋销售价格预测问题进行了深入研究与建模。项目以Kaggle提供的加州房屋 交易数据集为基础&#xff0c;通过数据清洗、特征工程、模型训练与评估等步骤&#xff0c;构建了一种基于集成学习策略的房价预测模…

Flink运行机制相关概念介绍

Flink运行机制相关概念介绍 1. 流式计算和批处理2. 流式计算的状态与容错3. Flink简介及其在业务系统中的位置4. Flink模型5. Flink的架构6. Flink的重要概念7. Flink的状态、状态分区、状态缩放&#xff08;rescale&#xff09;和Key Group8. Flink数据交换9. 时间语义10. 水位…

GrayLog日志平台的基本使用-接入jumpserver

1、jumpserver3.8.0部署 Docker 环境准备 # 安装依赖包 yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加源 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 替换Docker 安装源为清华大学镜像站 sed -i sh…

Spring Web MVC的入门学习(一)

目录 一、什么是 Spring Web MVC 1、MVC 定义 二、学习Spring MVC 1、项目准备 2、建立连接 2.1 RequestMapping 注解的学习 2.2 RequestMapping 使用 3、请求 3.1 传递单个参数 3.2 传递多个参数 3.3 传递对象 3.4 后端参数重命名&#xff08;后端参数映射&#xf…

1111111

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

配置code-server和texlive实现网页写tex

使用overleaf太卡了&#xff0c;有云服务器或者nas小主机&#xff0c;配置自己的code-servertexlive&#xff0c;来写论文。 之前用服务器配置过自己的overleaf&#xff0c;感觉不是很好用&#xff0c;缺少东西。 一、思路 使用docker先安装一个ubuntu&#xff0c;用dockerfil…

day63 单调栈part02

503. 下一个更大元素 II 中等 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&#xff0c;这个数字之后的第一个比它更…

【generate】如何维护一套icon组件库,直接输出svg为react component

https://github.com/ant-design/ant-design-web3/pull/761/files 实现了icon-preview(通过jsdoc, 鼠标放在组件上可以看到icon的样式)&#xff0c;因为打包方式、产物以及命名上有一些不同&#xff0c;可能需要稍加改造。 这个同步脚本应该后续也用得上&#xff0c;略加改造同步…

Macbook文件清理软件 Mac电脑清理垃圾文件怎么清理

为了维护Macbook电脑的系统健康&#xff0c;我们需要定期给电脑进行全面清理&#xff0c;清除系统垃圾文件、软件缓存和系统内存。那么好用的Macbook文件清理软件有哪些呢&#xff1f;今天就给大家介绍几款好用的电脑清理软件并介绍Mac电脑清理垃圾文件怎么清理。 一、Macbook…

29-控制流(下):iam-apiserver服务核心功能实现讲解

我们再来看下 iam-apiserver 中的核心功能实现。 这些关键代码设计分为 3 类&#xff0c;分别是应用框架相关的特性、编程规范相关的特性和其他特性。 应用框架相关的特性 应用框架相关的特性包括三个&#xff0c;分别是优雅关停、健康检查和插件化加载中间件。 优雅关停 …

基于SpringBoot和Vue的教务网络管理系统的设计与实现【附源码】

1、系统演示视频&#xff08;演示视频&#xff09; 2、需要交流和学习请联系

00-JAVA基础-注解及反射解析注解

注解 什么是注解 Java 注解&#xff08;Annotation&#xff09;是 JDK 5.0 引入的一种元素&#xff0c;用于为 Java 代码提供元数据。元数据是关于数据的数据&#xff0c;它为代码提供附加信息&#xff0c;而这些信息并不直接参与到程序的逻辑中&#xff0c;但可以被编译器或…