深入理解JVM(五):类文件结构

今天我们来介绍一下Class类文件结构

1.概述

计算机虽然只能识别0和1,但是越来越多的程序语言选择了与操作系统和机器指令集无关的、平台中立的格式作为程序编译后的存储格式。Java虚拟机不和包括Java在内的任何语言绑定,只与“class文件”这种特定的二进制文件所关联,Class文件中包含了Java虚拟机指令集合符号表以及若干其他辅助信息。Java虚拟机作为一个通用的、机器无关的执行平台,任何其他语言都可以将其作为语言的产品交付媒介。

2.Class类文件结构

Class文件是一组以8位字节为基础的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙存在。当遇到占位8个字节以上空间的数据项时,会按照高位在前的方式分割为若干个8位字节进行存储。

Class文件结构不像XML等描述语言,由于它没有任何分割符号,所以无论是数量甚至于存储字节序这样的细节都被严格限定,哪个字节代表什么含义,长度是多少,先后顺序如何都不允许改变。

Class文件格式采取一种类似C语言结构体的伪结构体来存储数,这种伪结构有两种数据类型:无符号数和表

表是由多个无符号数和其他表作为数据项构成复合数据类型,所有表都习惯性地以"_info"结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质就是一张表。它由下表构成。
在这里插入图片描述

2.1 魔数与Class文件的版本

每个Class文件的头四个字节称为魔数,它唯一的作用是确定这个文件是否能被虚拟机接收的Class文件。紧接着的四个字节存储的是Class文件的版本号:第五和第六是次版本号,第七和第八是主版本号。

2.2 常量池

紧接着是常量池的入口,常量池可以理解为Class文件的中资源仓库,它是Class文件结构与其他项目关联最多的数据结构类型,也是占用Class文件最大的数据项目之一,同时它还是Class文件中第一个出现的表类型数据项目。常量池中主要存放两大常量:字符量和符号引用字面量比较接近Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

2.3 访问标志

在常量池结束后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层的访问信息,包括这个Class是类还是接口,是否为public或者abstract类型,如果是类的话是否声明为final等等。

2.4 类索引、父类索引与接口索引集合

类索引、父类索引与接口索引集合都按照顺序排列在访问标志之后,Class文件由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个父类的全限定名,由于java语言的单继承,所以父类索引就只有一个,除了java.lang.Object之外,所有的java类都有父类,因此除了java.lang.Object外,所有的java类的父类索引都不为0。接口索引集合用于描述这个类实现了哪些接口,这些被实现的接口将按照implements(如果这个类本身是接口的话则是extends)后的接口顺序从左到右排列在接口索引集合中。

2.5 字段表集合

字段表用于描述接口或者类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。

我们可以想一想在java中描述一个字段可以包含什么信息呢?

字段的作用域(public ,private,protected修饰符),是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型、字段名称。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型这些都是无法固定的,只能引用常量池中常量来描述。

2.6 方法表集合

Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方法。方法标的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。

在这里稍微提一下,因为volatile修饰符和transient修饰符不可以修饰方法,所以方法表的访问标志中没有这两个对应的标志,但是增加了synchronized、native、abstract等关键字修饰方法,所以也就多了这些关键字对应的标志。

2.7 属性表集合

在Class文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与Class文件中其它的数据项目要求的顺序、长 度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。

属性名称都是对应常量池中存放的字符。

3.字节码指令简介

在java虚拟机的指令集中,大多数的指令都包含了其操作对应的数据类型信息,例如iload指令用于从局部变量表中加载int类型的数据到操作数栈中,而fload指令加载的则是float类型的数据。

大部分的指令都没有支持整数类型byte、char、short甚至没有任何指令支持boolean类型。大多数对于byte、char、short、Boolean类型的操作,实际上都是使用对应的int类型作为运算符类型。

3.1 加载和存储指令

加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。

3.2 运算指令

运算或者算术指令用于对操作数栈上的值进行某种特定的运算,并把结果重新存入操作栈顶。入操作栈顶。 大体上算术指令可以分为两种:对整型数据和对浮点数据进行运算指令。(由于没有byte、char、short、boolean类型,所以对这类数据的运算应使用int类型指令代替)

3.3 类型转换指令

类型转换指令可以将两种不同的数值类型进行相互转换。(比如int类转换成float类型)

3.4 对象创建和访问指令

虽然类实例和数组都是对象,但是java虚拟机对类实例和数组的创建和操作使用了不同的字节码。

3.5 操作数栈管理指令

如同操作数据结构中的栈一样,java虚拟机也提供了一些直接操作操作数栈的指令。

3.6 控制转移指令

可以认为控制转移指令就是在有条件或者无条件的修改PC寄存器的值。

3.7 方法调用和返回指令

  • invokevirtual 指令用于调用对象的实例方法
  • invokeinterface指令用于调用接口方法
  • invokespecial指令用于调用一些需要特殊处理的实例方法
  • invokestatic指令用于调用类方法(static方法)
  • invokedynamic指令用于在运行时动态解析出调用点限定符所使用的方法。

方法调用指令与数据类型无关,而方法返回指令是根据返回值的类型区分的。

3.8 异常处理指令

在java虚拟机中,处理异常不是由字节码指令来实现的,而是采用异常表的方式。

3.9 同步指令

java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构试验观城来支持的。

猜你喜欢

转载自blog.csdn.net/jdfk423/article/details/83340703