jvm 虚拟机执行子系统 类文件结构(转载)
https://blog.csdn.net/weixin_44577413/article/details/114058224
jvm 虚拟机执行子系统 类文件结构
渣渣付 2021-02-25 08:56:49 6 收藏
分类专栏: java学习 面向对象设计 文章标签: jvm java
版权
虚拟机执行子系统
类文件结构
无关性基石 (了解性知识)
平台无关性
各种不同平台的Java虚拟机, 以及所有平台都统一支持的程序存储格式——字节码(Byte Code)是构成平台无关性的基石
语言无关性 仍然是虚拟机和字节码存储格式
Class文件结构
Class文件是一组以8个字节为基础单位的二进制流
各个数据项目严格按照顺序紧凑地排列在文件之中, 中间没有添加任何分隔符, 这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据, 没有空隙存在。
当遇到需要占用8个字节以上空间的数据项时, 则会按照高位在前的方式分割成若干个8个字节进行存储。
Class文件格式采用一种类似于C语言结构体的伪结构来存储数据, 这种伪结构中只有两种数据类型: “无符号数”和“表”
无符号数属于基本的数据类型
以u1、 u2、 u4、 u8来分别代表1个字节、 2个字节、 4个字节和8个字节的无符号数
无符号数可以用来描述数字、 索引引用、 数量值或者按照UTF-8编码构成字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型
无论是无符号数还是表, 当需要描述同一类型但数量不定的多个数据时, 经常会使用一个前置的容量计数器加若干个连续的数据项的形式, 这时候称这一系列连续的某一类型的数据为某一类型的“集合”。
魔数
每个Class文件的头4个字节被称为魔数(Magic Number) , 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。
Class文件的版本
紧接着魔数的4个字节存储的是Class文件的版本号
第5和第6个字节是次版本号(Minor Version)
第7和第8个字节是主版本号(Major Version)
高版本的JDK能向下兼容以前版本的Class文件, 但不能运行以后版本的Class文件
即使文件格式并未发生任何变化, 虚拟机也必须拒绝执行超过其版本号的Class文件。
常量池
紧接着主、 次版本号之后的是常量池入口
常量池可以比喻为Class文件里的资源仓库,
是Class文件结构中与其他项目关联最多的数据
通常也是占用Class文件空间最大的数据项目之一
还是在Class文件中第一个出现的表类型数据项目。
常量池中主要存放两大类常量:
字面量
字面量比较接近于Java语言层面的常量概念, 如文本字符串、 被声明为final的常量值等。
符号引用
符号引用则属于编译原理方面的概念, 主要包括下面几类常量:
被模块导出或者开放的包(Package)
类和接口的全限定名(Fully Qualified Name) 包名+类名
字段的名称和描述符(Descriptor)
方法的名称和描述符
方法句柄和方法类型(Method Handle、 Method Type、Invoke Dynamic)
动态调用点和动态常量
Class文件中不会保存各个方法、字段最终在内存中的布局信息
访问标志
在常量池结束之后, 紧接着的2个字节代表访问标(access_flags) , 这个标志用于识别一些类或者接口层次的访问信息, 包括: 这个Class是类还是接口; 是否定义为public类型; 是否定义为abstract类型; 如果是类的话, 是否被声明为final
类索引、 父类索引与接口索引集合
类索引(this_class) 和父类索引(super_class) 都是一个u2类型的数据, 而接口索引集合
(interfaces) 是一组u2类型的数据的集合, Class文件中由这三项数据来确定该类型的继承关系。
字段表集合
方法表集合
属性表集合
字节码指令
Java虚拟机的指令由一个字节长度的、 代表着某种特定操作含义的数字(称为操作码, Opcode)
以及跟随其后的零至多个代表此操作所需的参数(称为操作数, Operand) 构成。
面向操作数栈并非面向寄存器
很多会和数据类型相关
boolean byte short char 用int的字节码指令来处理
公有设计,私有实现
虚拟机实现的方式主要有以下两种:
将输入的Java虚拟机代码在加载时或执行时翻译成另一种虚拟机的指令集;
将输入的Java虚拟机代码在加载时或执行时翻译成宿主机处理程序的本地指令集(即即时编译器代码生成技术) 。
类加载机制
Java虚拟机把描述类的数据从Class文件加载到内存, 并对数据进行校验、 转换解析和初始化, 最终形成可以被虚拟机直接使用的Java类型, 这个过程被称作虚拟机的类加载机制。
类型的加载、 连接和初始化过程都是在程序运行期间完成
类加载的时机
一个类型从被加载到虚拟机内存中开始, 到卸载出内存为止, 它的整个生命周期将会经历加载(Loading) 、 验证(Verification) 、 准备(Preparation) 、 解析(Resolution) 、 初始化(Initialization) 、 使用(Using) 和卸载(Unloading) 七个阶段, 其中验证、 准备、 解析三个部分统称为连接(Linking) 。
加载
在加载阶段, Java虚拟机需要完成以下三件事情:
通过一个类的全限定名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个代表这个类的java.lang.Class对象, 作为方法区这个类的各种数据的访问入口。
文件格式验证 验证是否是Class文件流
元数据验证 元数据信息进行语义校验
字节码验证 具体代码的语义校验
符号引用验证
发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段
连接
验证
验证是连接阶段的第一步, 这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》 的全部约束要求, 保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
准备
准备阶段是正式为类中定义的变量(即静态变量, 被static修饰的变量) 分配内存并设置类变量初始值的阶段, 从概念上讲, 这些变量所使用的内存都应当在方法区中进行分配
内存分配的仅包括类变量, 而不包括实例变量
给零值 如果被final修饰会初始化为定义的值
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,
符号引用(Symbolic References) : 符号引用以一组符号来描述所引用的目标, 符号可以是任何形式的字面量, 只要使用时能无歧义地定位到目标即可。 符号引用与虚拟机实现的内存布局无关, 引用的目标并不一定是已经加载到虚拟机内存当中的内容。 各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的, 因为符号引用的字面量形式明确定义在《Java虚拟机规
范》 的Class文件格式中。
直接引用(Direct References) : 直接引用是可以直接指向目标的指针、 相对偏移量或者是一个能间接定位到目标的句柄。 直接引用是和虚拟机实现的内存布局直接相关的, 同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。 如果有了直接引用, 那引用的目标必定已经在虚拟机的内存中存在。
初始化
会把static的赋值为对应的值
普通的开始先赋值为零值
遇到new、 getstatic、 putstatic或invokestatic这四条字节码指令时, 如果类型没有进行过初始化, 则需要先触发其初始化阶段。 能够生成这四条指令的典型Java代码场景有:
使用new关键字实例化对象的时候
读取或设置一个类型的静态字段(被final修饰、 已在编译期把结果放入常量池的静态字段除外)的时候。
调用一个类型的静态方法的时候。
使用java.lang.reflect包的方法对类型进行反射调用的时候, 如果类型没有进行过初始化, 则需要先触发其初始化。
当初始化类的时候, 如果发现其父类还没有进行过初始化, 则需要先触发其父类的初始化。
当虚拟机启动时, 用户需要指定一个要执行的主类( 包含main()方法的那个类) , 虚拟机会先初始化这个主类。
JDK动态语言支持 REF_getStatic、 REF_putStatic、REF_invokeStatic、 REF_newInvokeSpecial四种类型的方法句柄
当一个接口中定义了JDK 8新加入的默认方法( 被default关键字修饰的接口方法) 时, 如果有这个接口的实现类发生了初始化, 那该接口要在其之前被初始化。
使用
卸载
类加载器(重点)
类加载器用于实现类的加载动作
双亲委派
双亲委派模型要求除了顶层的启动类加载器外, 其余的类加载器都应有自己的父类加载器。 不过这里类加载器之间的父子关系一般不是以继承(Inheritance) 的关系来实现的, 而是通常使用组合(Composition) 关系来复用父加载器的代码。
破坏性双亲委派
————————————————
版权声明:本文为CSDN博主「渣渣付」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44577413/article/details/114058224