JVM 运行机制及基本原理(二)-Class文件结构-class文件

JVM 运行机制及基本原理(二)-Class文件结构-class文件

Class文件简介

Class文件由Java编译器生成,即我们在命令行运行JDK的javac命令,编写的源代码(.Java文件)在经过Java编译后,生成符合JVM规范的(.Class)文件,符合JVM规范的(.Class)文件能被JVM所识别和运行。

每个Class文件都对应着唯一一个类或接口的定义信息,但是,类或接口并不一定都定义在文件里(例如:类或接口可以通过类加载器直接生成)。我们只是简单地将任意一个有效的类或接口所应当满足的格式称为“Class文件格式”,即使它不一定以磁盘文件的形式存在。

Class文件结构

(1)概述

每个Class文件由8字节为单位的字节流组成,所有的16位、32位和64位长度的数据将被构造成 2个、4个和8个字节单位来表示。多字节数据项总是按照Big-Endian的顺序进行存储。

Big-Endian顺序是指按高位字节在地址最低位,最低字节在地址最高位来存储数据,它是SPARC、PowerPC等处理器的默认多字节存储顺序,而x86等处理器则是使用了相反的Little-Endian顺序来存储数据。

为了保证Class文件在不同硬件上具备同样的含义,因此在Java虚拟机规范中是有必要严格规定了数据存储顺序的。

为了便于描述,我们用u1,u2和u4,分别代表了1、2和4个字节的无符号数。

Class文件格式采用类似C语言结构体的伪结构来描述Class文件格式。为了避免与类的字段、类的实例等概念产生混淆,把用于描述类结构格式的内容定义为项(Item)。在Class文件中,各项(Item)严格按照顺序连续存放,它们之间没有任何填充或对齐作为各项间的分隔符号。

表(Table)是由任意数量的可变长度的项(Item)组成,用于表示Class文件内容的一系列复合结构。虽然采用类似C语言的数组语法来表示表(Table)中的项(Item),但是我们应当清楚意识到,表是由可变长数据组成的复合结构(表中每项(Item)的长度不固定),因此无法直接将字节偏移量来作为索引对表进行访问。而我们描述一个数据结构为数组(Array)时,就意味着它含有零至多个长度固定的项组成,这个时候则可以采用数组索引的方式来访问它。

(2)Class文件组成结构

 ClassFile { 
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

magic(魔数)

魔数的唯一作用是标记这个文件是否为一个能被Java虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE。

minor_version(副版本号)、major_version(主版本号)

minor_version和major_version的值分别表示Class文件的副版本、主版本。它们共同构成了Class文件格式的版本号。例如:某个Class文件的主版本号为M,副版本号为m,那么这个Class文件的格式版本号就确定为M.m。Class文件格式版本号大小的顺序为:1.5 < 2.0 < 2.1。

一个Java虚拟机实例只能支持特定范围内的主版本号(Mi至Mj)和0至特定范围内(0至m)的副版本号。假设一个Class文件的格式版本号为V,仅当Mi.0 ≤ v ≤Mj.m成立时,这个Class文件才可以被此Java虚拟机支持。不同版本的Java虚拟机实现,支持的版本号也不同,高版本号的Java虚拟机实现可以支持低版本号的Class文件,反之则不成立。

constant_pool_count

常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。constant_pool表的索引值只有在大于0且小于constant_pool_count时才会被认为是有效的(值为0的constant_pool索引是无效的,但其他用到常量池的数据结构可以使用索引0来表示“不引用任何一个常量池项”的意思),对于long和double类型有例外情况。

constant_pool[]

常量池,constant_pool是一种表结构,包含Class对象及其子对象中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池中的每一项都有相同的格式特征(第一个字节作为类型标记,用于识别该项是哪种类型的常量,称为“tag byte”)。常量池的索引范围是1至constant_pool_count−1。

access_flags

访问标志,access_flags是一种掩码标志,用于表示某个类、接口或基础属性的访问权限。access_flags的取值范围和相应含义见下表。

 标记名 值 含义
ACC_PUBLIC 0x0001 可以被包的类外访问。
ACC_FINAL 0x0010 不允许有子类。
ACC_SUPER 0x0020 当用到invokespecial指令时,需要特殊处理的父类方法。
ACC_INTERFACE 0x0200 标识定义的是接口而不是类。
ACC_ABSTRACT 0x0400 标记抽象类,不能被实例化。
ACC_SYNTHETIC 0x1000 标识并非Java源码生成的代码。
ACC_ANNOTATION 0x2000 标识注解类型
ACC_ENUM 0x4000 标识枚举类型

ACC_SYNTHETIC标志的类,是由编译器自己产生而不是由程序员编写的源代码生成的。

ACC_ENUM标志的类,表示该结构或它的父类被声明为枚举类型。

ACC_INTERFACE标志的类,代表该结构是接口而不是类,反之是类而不是接口。如果一个Class文件被设置了ACC_INTERFACE标志,同时也得设置ACC_ABSTRACT标志,而且不能再设置ACC_FINAL、ACC_SUPER 和 ACC_ENUM标志。

ACC_ANNOTATION标记的是注解类型,如果设置ANNOTATION标记,ACC_INTERFACE也必须被同时设置。如果没有同时设置ACC_INTERFACE标记,那么这个Class文件可以具有表中的除ACC_ANNOTATION外的所有其它标记。当然ACC_FINAL和ACC_ABSTRACT这类互斥的标记除外。

ACC_SUPER标志用于确定该Class文件里面的invokespecial指令使用的是哪一种执行语义。目前Java虚拟机的编译器都应当设置这个标志。ACC_SUPER标记是为了向后兼容旧编译器编译的Class文件而存在的,在JDK1.0.2版本以前的编译器产生的Class文件中,access_flag里面没有ACC_SUPER标志。同时,JDK1.0.2前的Java虚拟机遇到ACC_SUPER标记会自动忽略它。

在表中没有使用的access_flags标志位是为未来扩充而预留的,这些预留的标志为在编译器中会被设置为0, Java虚拟机实现也会自动忽略它们。

this_class

类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类或接口。

super_class

父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。

如果它的值不为0,那constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类的直接父类。当前类的直接父类,以及它所有间接父类的access_flag中都不能带有ACC_FINAL标记。

对于接口来说,它的Class文件的super_class项的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为代表java.lang.Object的CONSTANT_Class_info类型常量。

如果Class文件的super_class的值为0,那这个Class文件只可能是定义的是java.lang.Object类,只有它是唯一没有父类的类。

interfaces_count

接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量。

interfaces[]

接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量,其中0 ≤ i < interfaces_count。在interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即interfaces[0]对应的是源代码中最左边的接口。

fields_count

字段计数器,fields_count的值表示当前Class文件fields[]数组的成员个数。fields[]数组中每一项都是一个field_info结构的数据项,它用于表示该类或接口声明的类字段或者实例字段。

fields[]

字段表,fields[]数组中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。

fields[]数组描述当前类或接口声明的所有字段,但不包括从父类或父接口继承的部分。

methods_count

方法计数器,methods_count的值表示当前Class文件methods[]数组的成员个数。Methods[]数组中每一项都是一个method_info结构的数据项。

methods[]

方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。

如果某个method_info结构的access_flags项既没有设置ACC_NATIVE标志也没有设置ACC_ABSTRACT标志,那么它所对应的方法体就应当可以被Java虚拟机直接从当前类加载,而不需要引用其它类。

method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法方法和类或接口初始化方法方法。

methods[]数组只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。

attributes_count

属性计数器,attributes_count的值表示当前Class文件attributes表的成员个数。attributes表中每一项都是一个attribute_info结构的数据项。

attributes[]

属性表,attributes表的每个项的值必须是attribute_info结构。

在Class文件规范里,Class文件结构中的attributes表的项包括下列定义的属性:

 InnerClasses
EnclosingMethod
Synthetic
Signature
SourceFile
SourceDebugExtension
Deprecated
RuntimeVisibleAnnotations
RuntimeInvisibleAnnotations
BootstrapMethods

对于支持Class文件格式版本号为49.0或更高的Java虚拟机实现,必须正确识别并读取attributes表中的Signature、RuntimeVisibleAnnotations和 RuntimeInvisibleAnnotations属性。

对于支持Class文件格式版本号为51.0或更高的Java虚拟机实现,必须正确识别并读取attributes表中的BootstrapMethods属性。

Class文件规范要求任一Java虚拟机实现可以自动忽略Class文件的attributes表中的若干(甚至全部)它不可识别的属性项。任何Class文件规范未定义的属性不能影响Class文件的语义,只能提供附加的描述信息。

推荐阅读