JVM-类文件结构

java虚拟机不仅可以运行java程序,还可以运行其他语言编写的程序。字节码是构成平台无惯性的基石,class文件是由字节码组成的。

java虚拟机不与任何程序语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。

Class类文件的结构

Class文件是一组以字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符

Class文件中只有两种数据类型:

  • 无符号数

    是基本数据类型,u1,u2,u4,u8分别代表1个字节(8bit),2个字节,4个字节,8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值、utf8编码构成的字符串值。

  • 由多个无符号数或者其他表作为数据项构成的复合数据类型。表的命名一般以“info”结尾。整个class文件本身也可以视作一张表。

Class文件格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ClassFile {
u4 magic; //Class 文件的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的访问标记
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口
u2 fields_count;//Class 文件的字段属性
field_info fields[fields_count];//一个类会可以有多个字段
u2 methods_count;//Class 文件的方法数量
method_info methods[methods_count];//一个类可以有个多个方法
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
}

几个经常会提到的概念:

  1. 全限定类名

    把全类名中的 . 换成 / 即可。比如Object类的全类名为java.lang.Object,那么它的全限定类名就是java/lang/Object。一般会在全限定类名后面加上 ; 表示这个全限定类名的结束,避免与其他全限定类名混淆。

  2. 简单名称

    没有类型和参数修饰的方法或者字段名称,比如有一个类中的方法inc(),一个类中的字段m,他们的简单名称分别是inc,m。

  3. 方法和字段的描述符

    (1)基本数据类型和方法的返回值是基本数据类型、void、对象的描述符

    方法返回值是对象类型,则返回值的描述符是字符L加对象的全限定类名。

    (2)数组类型

    每一维度使用一个前置的 [ 字符来描述,比如一个Int型数组int[]的描述符为”[I”,引用类型的数组String[] []的描述符为 “[[Ljava.lang.Object;”

    (3)用描述符描述整个方法

    按照先参数列表、后返回值的顺序描述,void inc()的描述符就是”()V”,int index(char[] a,int b,int c,char[] d,int f)的描述符为”([CII[CI)I”

1.魔数

1
u4             magic; 

class文件的头4个字节被称为魔数,它的作用是确定这个文件是否为一个能被虚拟机接收的Cass文件。其他文件比如GIF等也使用魔数来识别文件。

使用魔数而不是扩展名.class来识别文件是否是Class文件的原因是:文件扩展名可以随意改动,不安全。Class文件的魔数值为0xCAFEBABE。

2.Class文件的版本

1
u2             minor_version;

第五、六字节是次版本号,JDK1.2-JDK12,次版本号都被固定为0

1
u2             major_version;

第七、八字节是主版本号

java的主版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号加1,比如JDK13可以生成的Class文件的主版本号为45+12=57。高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件。

3.常量池

1
2
 u2             constant_pool_count;//常量池的数量
cp_info constant_pool;//常量池

常量池的容量是从1开始计数的,将第0项常量空出来是为了后面某些指向常量池的索引值的数据再特定情况下需要表达“不引用任何一常量池项目”的含义,可把索引值设置为0来表示。

假如常量池的数量为22,减去第0项常量,所以常量池中有21项可用常量,索引为1~21

常量池中主要存放的是:

  • 字面量

    文本字符串、final的常量值

  • 符号引用

    类和接口的全限定类名

    字段的名称和描述符

    方法的名称和描述符

    被模块导出或者开放的包

下面是常量池中存放的类型。每一项常量结构中第一项都是一个u1的tag,用来标志它是下表中的哪一个结构。

4.访问标志

1
u2             access_flags;

常量池结束后,是访问标志,用于标识一些类或者接口层次的访问信息

access_flags的最终值等于所有为真的标志的值相与。

5.当前类(This Class)索引、父类(Super Class)索引、接口(Interfaces)索引集合

1
2
3
4
u2             this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces;//一个类可以实现多个接口

Class文件用这三项数据来确定该类型的继承关系。

类索引用于确定这个类的全限定类名,虚拟机得到u2的值后,换算为十进制,到常量池中找index为这个值的那一行,这一行是一个类型为CONSTANT_Class_info的类描述常量符,通过这个类型中的index找到CONSTANT_Utf8_info类型的字符串常量,就是全限定类名。

父类索引用于确定这个类的父类的全限定类名,确定操作和类索引的一样。

接下来的两个字节是接口计数器,表示索引表的容量,如果该类没有实现接口,那这个值是0,后面接口的索引表就不再占用任何字节,确定操作和类索引的一样。

6.字段表集合

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

1
2
u2             fields_count;//Class 文件的字段的个数
field_info fields;//一个类会可以有个字段

access_flags字段访问标志符有这几种情况。

name_index和descriptor_index都是对常量池的引用,需要去常量池中找,分别代表字段的简单名称和字段和方法的描述符。

attributes_count: 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;

attributes_info: 存放具体属性具体内容。比如它的值是123,就存一个指向123的指针。

7.方法表集合

1
2
u2             methods_count;//Class 文件的方法的数量
method_info methods;//方法表

access_flags的类型有

8.属性表集合

1
2
u2             attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合

Class文件,方法表,字段表都可以有属性表集合。

属性表的结构

Code属性

使用位置:方法表的属性集合中

java程序方法体中的代码经过javac编译器处理后,最终变为字节码指令存储在Code属性中。不过并非所有的方法表都存在这个属性,比如接口或者抽象类的方法中就不存在Code属性。

u2 attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,此常量值固定为“Code”,它 代表了该属性的属性名称

u4 attribute_length指示了属性值的长度,由于属性名称索引与属性长度一共为 6个字节,所以属性值的长度固定为整个属性表长度减去6个字节。

u2 max_stack代表了操作数栈(Operand Stack)深度的最大值。在方法执行的任意时刻,操作数栈都 不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度。

u2 max_locals代表了局部变量表所需的存储空间。在这里,max_locals的单位是变量槽(Slot),当局部变量表空间不足时,变量槽会被重用。

java编译器根据 同时生存的最大局部变量数量和类型计算出max_locals的大小

方法参数(包括实例方法中的隐藏参数“this”)、显式异常处 理程序的参数(Exception Handler Parameter,就是try-catch语句中catch块中所定义的异常)、方法体中 定义的局部变量都需要依赖局部变量表来存放。

u4 code_length和code用来存储Java源程序编译后生成的字节码指令。code_length代表字节码长度, code是用于存储字节码指令的一系列字节流。

虽然它是一个u4类型的长度值,理论上最大值可以达 到2的32次幂,但是《Java虚拟机规范》中明确限制了一个方法不允许超过65535条字节码指令,即它 实际只使用了u2的长度,如果超过这个限制,Javac编译器就会拒绝编译。

u2 attributes_count表示Code的属性表的容量

attribute_info attributes是Code的属性表,包括以下内容

  • LineNumberTable属性

    LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。 它并不是运行时必需的属性,但默认会生成到Class文件之中

  • LocalVariableTable及LocalVariableTypeTable属性

    LocalVariableTable属性用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,它 也不是运行时必需的属性,但默认会生成到Class文件之中

等等…………..书上还有很多属性,这里不一一介绍了。

还有字节码指令他们的功能和种类,可以去看《深入理解java虚拟机》P251


JVM-类文件结构
https://vickkkyz.fun/2022/03/24/Java/JVM/4.类文件/第六章/
作者
Vickkkyz
发布于
2022年3月24日
许可协议