class类文件结构(这是一篇非常枯燥的文章)

我们知道,Java文件编译后会产生一个字节码文件(.class文件),本文介绍字节码文件的文件结构相关内容。

前言

Java诞生之初就宣称的“一次编译,处处运行”的性质一直是Java的一大特点,而Java实现这个特点的方式是将.java文件编译成.class文件,通过JVM屏蔽系统差异实现的。事实上,不仅是Java,其他的比如Kotlin、Groovy等语言也都可以通过编译成字节码文件运行在Java虚拟机上,而Java虚拟机也并不关心被编译成字节码文件之前是什么语言。

不同语言的编译与加载

类文件结构

按照Java虚拟机规范,class文件结构一般是向后兼容的,新版本的Java虚拟机规范一般只是在旧版基础上进行扩充,而不对之前内容修改,因此类文件结构从第一版开始定义的一些细节几乎没有变化。我们先来看一下class文件格式。

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];//属性表集合
}

上面的u2u4分别代表两个字节和四个字节的无符号数,_info结尾表示是一个表结构,表结构可以理解为一个类,用于表示复合数据结构的数据。

class文件字节码结构组织示意图

上图是class文件字节码结构组织示意图,结构非常清楚。接下来为大家依次介绍class文件结构的各个部分。

魔数

    u4             magic; //Class 文件的标志:魔数

字节码文件前4个字节被称为魔数(magic number),作用是让JVM能够识别这是一个字节码文件。其实不仅是字节码文件,一些图片的格式也存在魔数。字节码文件的魔数比较可爱,它的值为0xcafebabe(咖啡宝贝)。

Class文件版本

    u2             minor_version;//Class 的次版本号
    u2             major_version;//Class 的主版本号

跟在魔数后面的四个字节表示Class文件的版本号,前两个字节是次版本号,后两个字节是主版本号。高版本的JDK可以支持低版本的Class文件,而低版本的JDK无法支持高版本为Class文件。

常量池

    u2             constant_pool_count;//常量池的数量
    cp_info        constant_pool[constant_pool_count-1];//常量池

constant_pool_count被用来表示常量池的常量数,这里的常量池是从1开始索引的,因为第0项是用于某些指向常量池的索引值的数据在特定情况下表示“不引用任何一个常量池项目”的含义。

常量池中存放两种类型的常量:字面量和符号引用。前者很接近Java语言对于常量的定义(字符串、被final修饰的常量等),后者则倾向于编译原理方面,主要包括:

  • 被模块导出或者开放的包(Package)

  • 类和接口的全限定名(Fully Qualified Name)

  • 字段的名称和描述符(Descriptor)

  • 方法的名称和描述符

  • 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)

  • 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-ComputedConstant)

常量池的每一个常量都是一个表,表结构起始的第一位是个u1类型的标志位(tag),代表着当前常量属于哪种常量类型。常见常量类型所代表的具体含义如下表所示。

类型 标志(tag) 描述
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 长整型字面量
CONSTANT_Double_info 双精度浮点型字面量
CONSTANT_Class_info 类或接口的符号引用
CONSTANT_String_info 字符串类型字面量
CONSTANT_Fieldref_info 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MothodType_info 16 标志方法类型
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

访问标志

    u2             access_flags;//Class 的访问标志

访问标志的部分比较简单,主要用来标记类或接口的访问信息,是类还是接口,是否是public类型,是否定义为final,是否是abstract类型等。

访问标志

定义一个普通的用public修饰的Java类,并且用JDK1.2以后的编译器编译,那么除了ACC_PUBLICACC_SUPER为真,其它全为假。

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

    u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个类可以实现多个接口

类索引和父类索引分别用来确定当前类和父类的全限定名,所谓全限定名就是类的路径全名,例如String类的全限定名是java/lang/String。由于除了Object类以外的所有类都有父类,所以只有Object类的父类索引为0。接口索引集合定义了类实现了哪些接口。

字段表集合

    u2             fields_count;//Class 文件的字段属性
    field_info     fields[fields_count];//一个类会可以有个字段

字段表用来描述类/接口中的成员变量,不是描述方法中的局部变量。下面是field_info的结构:

field_info结构

access_flag是用于确定字段的访问修饰符、是否静态变量、是否可变(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。下面是access_flag的取值:

access_flag的取值

name_index是对常量池的引用,用于描述字段的名称。

descriptor_index也是对常量池的引用,是字段和方法的描述符。

attributes_countattributes[attributes_count]是用于存放字段的一些属性。

方法表集合

    u2             methods_count;//Class 文件的方法数量
    method_info    methods[methods_count];//一个类可以有个多个方法

方法表用来描述类中的方法,method_info结构和field_info几乎一样,这里就不过多介绍了。

方法表的结构

下面是方法表的access_flag取值:

方法表的access_flag的所有标志位

属性表集合

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

属性表(attribute_info)在前面的讲解之中已经出现过数次,Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。

与Class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格顺序,并且《Java虚拟机规范》允许只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。

猜你喜欢

转载自blog.csdn.net/qq_32273417/article/details/106874249