类加载的时机与过程

news2024/9/20 18:37:29

                                                  ------ 摘自  周志明 《深入理解Java虚拟机》

类加载的时机

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载

(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化

(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三

个部分统称为连接(Linking)。这七个阶段的发生顺序如图7-1所示。

加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种

顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,

这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。请注意,这里笔者

写的是按部就班地“开始”,而不是按部就班地“进行”或按部就班地“完成”,强调这点是因

为这些阶段通常都是互相交叉地混合进行的,会在一个阶段执行的过程中调用、激活另一个阶段。

类加载的过程

加载

“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,希望读者没有混淆

这两个看起来很相似的名词。在加载阶段,Java虚拟机需要完成以下三件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流。

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

加载阶段结束后,Java虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中了,方法

区中的数据存储格式完全由虚拟机实现自行定义,《Java虚拟机规范》未规定此区域的具体数据结构。

型数据妥善安置在方法区之后,会在Java堆内存中实例化一个java.lang.Class类的对象,这个对象将作为

程序访问方法区中的类型数据的外部接口。

验证

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》

的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。验证阶段大致上会完成下面四

个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。

  1. 文件格式验证

    第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。该验证阶段的主

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

阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证之后,这段字节流才被允许进入Java虚拟

机内存的方法区中进行存储,所以后面的三个验证阶段全部是基于方法区的存储结构上进行的,不会再直接读取、

操作字节流了。

  1. 元数据验证

      第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求,这个阶段

可能包括的验证点如下:

·这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。

·这个类的父类是否继承了不允许被继承的类(被final修饰的类)。

·如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。

·类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方

法重载,例如方法参数都一致,但返回值类型却不同等)。

·……

第二阶段的主要目的是对类的元数据信息进行语义校验,保证不存在与《Java语言规范》定义相悖的元数据信息。

  1. 字节码验证

     

第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流分析和控制流分析,确定程序语义是合法

的、符合逻辑的。在第二阶段对元数据信息中的数据类型校验完毕以后,这阶段就要对类的方法体(Class文件

中的Code属性)进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。

  1. 符号引用验证

最后一个阶段的校验行为发生在虚拟机将符号引用转化为直接引用[3]的时候,这个转化动作将在连接的第三阶段

——解析阶段中发生。符号引用验证可以看作是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配

性校验,通俗来说就是,该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。本阶段通常需

要校验下列内容:

·符号引用中通过字符串描述的全限定名是否能找到对应的类。

·在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段。

·符号引用中的类、字段、方法的可访问性(private、protected、public、<package>)是否可被当

前类访问。

·……

符号引用验证的主要目的是确保解析行为能正常执行,如果无法通过符号引用验证,Java虚拟机

将会抛出一个java.lang.IncompatibleClassChangeError的子类异常,典型的如:

java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。

验证阶段对于虚拟机的类加载机制来说,是一个非常重要的、但却不是必须要执行的阶段,因为

验证阶段只有通过或者不通过的差别,只要通过了验证,其后就对程序运行期没有任何影响了。如果

程序运行的全部代码(包括自己编写的、第三方包中的、从外部加载的、动态生成的等所有代码)都

已经被反复使用和验证过,在生产环境的实施阶段就可以考虑使用-Xverify:none参数来关闭大部分的

类验证措施,以缩短虚拟机类加载的时间。

准备

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

的阶段。

关于准备阶段,还有两个容易产生混淆的概念笔者需要着重强调,首先是这时候进行内存分配的

仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其

次是这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:

public static int value = 123;

那变量value在准备阶段过后的初始值为0而不是123,因为这时尚未开始执行任何Java方法,而把

value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法之中,所以把value赋

值为123的动作要到类的初始化阶段才会被执行。

上面提到在“通常情况”下初始值是零值,那言外之意是相对的会有某些“特殊情况”:如果类字段

的字段属性表中存在ConstantValue属性那在准备阶段变量值就会被初始化为ConstantValue属性所指定

的初始值,假设上面类变量value的定义修改为:

public static final int value = 123;

编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据Con-stantValue的设置

将value赋值为123。

解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。

·符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标符号可以是任何

形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引

用的目标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同,

但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规

范》的Class文件格式中。

·直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能

间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚

拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机

的内存中存在。

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

类符号引用进行,分别对应于常量池的CONSTANT_Class_info、CON-STANT_Fieldref_info、

CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、

CONSTANT_MethodType_info、CONSTANT_MethodHandle_info、CONSTANT_Dyna-mic_info和

CONSTANT_InvokeDynamic_info 8种常量类型

初始化

类的初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶

段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控

制。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程

序。

进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序

编码制定的主观计划去初始化类变量和其他资源。我们也可以从另外一种更直接的形式来表达:初始化

阶段就是执行类构造器<clinit>()方法的过程。<clinit>()并不是程序员在Java代码中直接编写的方法,

它是Javac编译器的自动生成物。

·<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的

语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的静态语句块中只能访问

到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访

,如代码清单7-5所示。

代码清单7-5 非法前向引用变量

public class Test {
static {
i = 0; // 给变量复制可以正常编译通过
System.out.print(i); // 这句编译器会提示“非法向前引用”
}
static int i = 1;
}

·<clinit>()方法与类的构造函数(即在虚拟机视角中的实例构造器<init>()方法)不同,它不需要显

式地调用父类构造器,Java虚拟机会保证在子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行

完毕。因此在Java虚拟机中第一个被执行的<clinit>()方法的类型肯定是java.lang.Object。

·接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法。

但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,因为只有当父接口

中定义的变量被使用时,父接口才会被初始化。此外,接口的实现类在初始化时也一样不会执行接口的

<clinit>()方法。

·Java虚拟机必须保证一个类的<clinit>()方法在多线程环境中被正确地加锁同步,如果多个线程同时去初始

化一个类,那么只会有其中一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线

程执行完毕<clinit>()方法。如果在一个类的<clinit>()方法中有耗时很长的操作,那就可能造成多个进程阻塞,

在实际应用中这种阻塞往往是很隐蔽的。

需要注意,其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出<clinit>()方法后,其他线程唤

醒后则不会再次进入<clinit>()方法。同一个类加载器下,一个类型只会被初始化一次。

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

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

相关文章

6、数组的常见运算

目录 一、数组的算术运算 二、数组的关系运算 三、数组的逻辑运算 一、数组的算术运算 &#xff08;1&#xff09;数组的加减运算&#xff1a;通过格式AB或A-B可实现数组的加减运算。但是运算规则要求数组A和B的维数相同。 示例1&#xff1a; A[1 2 3 4]B[2 4 6 8]C[1 1 …

三种简洁易行的方法解决基于Vue.js的组件通信

在总结Vue组件化编程的数据通信方面&#xff0c;看了网上的很多资料&#xff0c;都是讲父子组件的数据交互也就是参数传递&#xff0c;在组件的通信方面分几种情况&#xff0c;比如父子组件、非父子的兄弟组件、非父子的其他组件等等&#xff0c;这样看来&#xff0c;基于Vue.j…

STC15系列单片机EEPROM读写示例

STC15系列单片机EEPROM读写示例&#x1f33c;STC15手册有关EEPROM描述 &#x1f33e;STC15系列单片机内部集成了大容量的EEPROM&#xff0c;其与程序空间是分开的。利用ISP/IAP技术可将内部DataFlash当EEPROM&#xff0c;擦写次数在10万次以上。EEPROM可分为若干个扇区&#xf…

Android 蓝牙开发——蓝牙协议配置(七)

蓝牙主要分为两种模式&#xff0c;一种是媒体输出&#xff08;Source&#xff09;端&#xff0c;一种是媒体输入&#xff08;Sink&#xff09;端。也可以理解为服务端&#xff08;Server&#xff09;与客户端&#xff08;Client&#xff09;的关系。 蓝牙配置文件&#xff08;B…

4-1指令系统-指令格式

文章目录一.指令的基本格式1.结构2.长度3.根据操作数地址码数目分类&#xff08;1&#xff09;零地址指令&#xff08;2&#xff09;一地址指令&#xff08;3&#xff09;二地址指令&#xff08;4&#xff09;三地址指令&#xff08;5&#xff09;四地址指令二.扩展操作码指令格…

Maven学习(二):Maven基础概念

Maven基础概念一、仓库二、坐标三、全局setting与用户setting区别一、仓库 仓库&#xff1a;用于存储资源&#xff0c;包含各种jar包&#xff1b;仓库分类&#xff1a; 本地仓库&#xff1a;自己电脑上的存储仓库&#xff0c;连接远程仓库获取资源&#xff1b;远程仓库&#x…

信息论复习—离散信道及其容量

目录 信道的简介&#xff1a; 信道的分类&#xff1a; 离散无记忆信道&#xff08;DMC&#xff09;模型&#xff1a; 转移概率&#xff1a; 离散无记忆信道的转移矩阵 输出仅与当前的输入有关&#xff1a; 后验概率&#xff1a; 离散无记忆信道的后验概率矩阵 &#xf…

spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法

spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法 目录spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法出现问题之前出现的问题&#xff1a;解决办法&#xff1a;方案一&#xff1a;第一种是继承 spring-boot-starter-parent 然后 依赖覆盖方案…

怎么用Python测网速?

“speedtest-cli” 是一个 Python 的第三方库&#xff0c;它可以用来在命令行中测试网络速度。它使用了 Speedtest.net 的服务器来进行测速&#xff0c;并可以提供下载和上传速度、延迟、丢包率等信息。使用这个库可以很方便地在终端中测试网络速度&#xff0c;而无需在浏览器中…

轻量级代码生成器加测试数据生成器

轻量级代码生成器加测试数据生成器介绍代码生成常用注解基本使用全局控制属性模板文件相关属性模板文件配置模拟数据生成自定义词库索引注意事项从已经存在的表完成映射,生成模板代码使用步骤Gitee项目链接 介绍 本项目是一个轻量级代码生成器,并提供多种方式来完成模拟数据的…

力扣(LeetCode)2299. 强密码检验器 II(C++/Python3)

题目描述 模拟 仅当密码包含强密码的所有特性&#xff0c;它是一个 强 密码。提示我们&#xff0c;遍历密码&#xff0c;维护 444 个标志&#xff0c;标志记录特性。遍历结束&#xff0c;根据标志判断特性。 class Solution { public:bool strongPasswordCheckerII(string pa…

MySQL建立数据库时字符集和排序规则的选择

文章目录前言一、字符集&#xff1f;二、Mysql中的字符集1.Unicode与UTF8、UTF8MB4、UTF16、UTF32关系2.Mysql新建数据库时选择哪种字符集呢&#xff1f;三、Mysql排序规则四、Mysql查询字符集和排序规则常用的命令前言 在MySQL中&#xff0c;字符集和排序规则是区分开来的&am…

【ArcGIS 小贴士】Pro VS ArcMap及软件获取

有朋友问我&#xff0c;ArcGIS Pro是不是比ArcMap好用。 我觉得用Pro的感觉&#xff0c;用Pro的感觉比ArcMap Ribbon界面 Pro采用的是Ribbon用户界面&#xff0c;与现在的微软的Office软件相似&#xff0c;功能的组织更加清晰。10.x版本的ArcGIS则是传统的工具条界面 有些朋友…

day42|● 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

1049. 最后一块石头的重量 II 1.代码 class Solution { public:int lastStoneWeightII(vector<int>& stones) {int sum 0;for(int i: stones) {sum i;}int t sum;sum sum /2;vector<int>f(sum 1);for (int i 0; i < stones.size(); i) {for (int j …

【Pytorch基础(2)】张量的索引,切片与维度变换

一、张量的维度索引 张量的索引是从第零维度开始的。让我们来创建一个四维的张量做举例说明&#xff1a;torch.Tensor(2,3,64,64) 此时&#xff0c;这个张量可以表示两张边长为64的正方形彩色图像&#xff0c;具体来说&#xff0c;张量的第零维表示图像的数量&#xff1b;第一…

Apollo星火计划学习笔记——Control 专项讲解(LQR)

文章目录1. 算法相关基础1.1 一阶倒立摆1.2 二自由度车辆横向跟踪偏差模型1.3 车辆横向跟踪偏差模型1.4 车辆横向跟踪偏差倒车模型1.5 轮胎侧偏角与侧偏刚度1.6 LQR 线性二次型问题:2. LQR代码解析2.1 WriteHeaders&#xff08;调试过程中的状态量&#xff09;2.2 LatControlle…

Android APK 瘦身

Android APK 瘦身的几个方法将项目中的图片由png、jpg转为webp格式。如下操作&#xff1a;1.1选中图片或者含有图片的文件夹 右键选择Convert toWebP..1.2根据自身情况选择有损压缩还是无损压缩备注&#xff1a;官网连接&#xff1a;https://developer.android.google.cn/studi…

leetcode-每日一题-强密码检验器II(简单,数学逻辑)

如果一个密码满足以下所有条件&#xff0c;我们称它是一个 强 密码&#xff1a;它有至少 8 个字符。至少包含 一个小写英文 字母。至少包含 一个大写英文 字母。至少包含 一个数字 。至少包含 一个特殊字符 。特殊字符为&#xff1a;"!#$%^&*()-" 中的一个。它 不…

Golang 从菜鸟到大咖的必经之路_GO 语言的转义字符、注释、规范和代码风格要求

目录 一、GO 语言转义字符 A.Golang 常用的转义字符&#xff08;escape char&#xff09;: B.课程练习 二、Go 语言注释&#xff1a; A.注释&#xff08;Comment&#xff09;: B.Go 语言中的注释类型&#xff1a; C.注释不会被编译 D.shifttab 三、规范的代码风格要求…

聚焦儿童羽绒服产业,看用友YonSuite打造领先实践的数智创新小灯塔

有一种冷“是妈妈觉得你冷”。每每想起小时候&#xff0c;为了应对寒冷的冬季&#xff0c;都会“全副武装”&#xff0c;裹得厚厚的&#xff0c;里三层外三层。 放到如今&#xff0c;有了羽绒服的萌娃们&#xff0c;已不再像我们当年一样穿得厚厚的了。现在的年轻爸妈喜欢装扮…