JVM-Class文件结构细节

216 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情

JVM-Class文件结构细节

class文件结构概述

Class文件的结构并不是一成不变的,随着Java虚拟机的不断发展,总是不可避免地会对Class文件结构做出一些调整,但是其基本结构和框架是非常稳定的。

魔数、Class文件版本、常量池、访问标识(或标志)、类索引,父类索引,接口索引集合、字段表集合、方法表集合、属性表集合

class文件的魔数是什么?

Magic Number(魔数):class文件的标志 每个 Class 文件开头的4个字节的无符号整数称为魔数(Magic Number)

它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件。即:魔数是Class文件的标识符。

魔数值固定为0xCAFEBABE。不会改变。

如果一个Class文件不以0xCAFEBABE开头,虚拟机在进行文件校验的时候就会直接抛出以下错误: Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 1885430635 in class file StringTest

使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动。

如何确保高版本的JVM可执行低版本的class文件?

不同版本的Java编译器编译的Class文件对应的版本是不一样的。目前,高版本的Java虚拟机可以执行由低版本编译器生成的Class文件,但是低版本的Java虚拟机不能执行由高版本编译器生成的Class文件。否则JVM会抛出java.lang.UnsupportedClassVersionError异常。 (向下兼容)

在实际应用中,由于开发环境和生产环境的不同,可能会导致该问题的发生。因此,需要我们在开发时,特别注意开发编译的JDK版本和生产环境中的JDK版本是否一致。

class 文件版本号 紧接着魔数的 4 个字节存储的是 Class 文件的版本号。同样也是4个字节。第5个和第6个字节所代表的含义就是编译的副版本号minor_version,而第7个和第8个字节就是编译的主版本号major_version。

它们共同构成了class文件的格式版本号。譬如某个 Class 文件的主版本号为 M,副版本号为 m,那么这个Class 文件的格式版本号就确定为 M.m。

Java 的版本号是从45开始的,JDK 1.1之后的每个JDK大版本发布主版本号向上加1。

虚拟机JDK版本为1.k (k >= 2)时,对应的class文件格式版本号的范围为45.0 - 44+k.0 (含两端)。

常量池

常量池:存放所有常量

常量池是Class文件中内容最为丰富的区域之一。常量池对于Class文件中的字段和方法解析也有着至关重要的作用。

常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用Class文件空间最大的数据项目之一。

常量池表项中,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

为什么需要常量池计数器?
constant_pool_count (常量池计数器)

由于常量池的数量不固定,时长时短,所以需要放置两个字节来表示常量池容量计数值。
常量池容量计数值(u2类型):从1开始,表示常量池中有多少项常量。即onstant_pool_count=1表示常量池中有0个常量项。

image.png

其值为0x0016,掐指一算,也就是22。
需要注意的是,这实际上只有21项常量。索引为范围是1-21。为什么呢?

通常我们写代码时都是从0开始的,但是这里的常量池却是从1开始,因为它把第0项常量空出来了。这是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可用索引值0来表示。

常量池表 constant_pool [](常量池)

constant_pool是一种表结构,以 1 ~ constant_pool_count - 1为索引。表明了后面有多少个常量项。

常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References) 它包含了class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。常量池中的每一项都具备相同的特征。第1个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte (标记字节、标签字节)。

image.png

这里说明下符号引用和直接引用的区别与关联

符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。

直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了。