实战java虚拟机(四)——Class文件结构

前言


 对于Java虚拟机来说,Class文件是虚拟机的一个重要接口。无论使用何种语言开发,只要能将源文件编译成正确的Class文件,那么这种语言就可以在Java虚拟机上运行。

Class文件总体结构如下图所示

Java虚拟机规范中,Class文件使用一种类似于C语言结构体的方式进行描述,并且统一使用无符号整数作为基本数据类型,由u1,u2,u4,u8分别表示无符号单字节,2字节,4字节和8字节整数,对于字符串,使用u1数组进行表示。

因此,一个Class文件可以非常严谨地被描述成:

ClassFile{
    u4        magic;
    u2        minor_version;
    u2        major_version;
    u2        constant_pool_count;
    cp_info            constant_pool[constant_pool_count];
    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];
}

Class文件的结构严格按照该结构体的定义:(以二进制格式打开Class文件分析)

(1) 文件以一个4字节的Magic(魔数)开头,紧跟着大、小版本号。

(2) 在版本号之后是常量池,常量池的个数为constant_pool_count,常量池中的表项有constant_pool_count -1 项。

(3) 常量池之后是类的访问修饰符、代表自身类的引用、父类引用及接口数量和实现的接口引用。

(4) 在接口之后,有字段的数量和字段描述、方法数量及方法的描述。

(5) 存放类文件的属性信息

 

一、魔数


 魔数作为Class文件的标志,用来告诉Java虚拟机,这是一个Class文件。它是4字节的无符号整数,固定为0xCAFEBABE

当一个文件不以0xCAFEBABE开头,则虚拟机进行文件校验时就会抛出以下错误:

Exception in thread “main” java.lang.ClassFormatError: Incompatible magic value 184466110 in class file ***

 

二、 版本


 魔数后面紧跟的是小版本号与大版本号,首先是小版本号,然后是大版本号,它们都是2字节的无符号整数

Class文件的版本号与Java编译器的对应关系如下表

大版本号

小版本号

编译器版本

46

0

1.2

47

0

1.3

48

0

1.4

49

0

1.5

50

0

1.6

51

0

1.7

52

0

1.8

53

0

1.9

54

0

10

 

这是从我项目中随机取的一个class文件,通过观察第一行魔数后面的4个字节可知,0x34 = 52 十进制,因此是1.8版本编译的

 

三、常量池


 常量池是Class文件内容最丰富的区域之一,它对于Class文件中字段和方法的解析也有至关重要的作用,版本号之后紧跟的是常量池的数量,以及若干个常量池表项

常量池表项的类型及其TAG值如下表

常量池类型

TAG

CONSTANT_Class

7

CONSTANT_Methodref

10

CONSTANT_String

8

CONSTANT_Float

4

CONSTANT_Double

6

CONSTANT_Utf8

1

CONSTANT_MethodType

16

CONSTANT_Fieldref

9

CONSTANT_InterfaceMethodref

11

CONSTANT_Integer

3

CONSTANT_Long

5

CONSTANT_NameAndType

12

CONSTANT_MethodHandle

15

CONSTANT_InvokeDynamic

18

继续使用上一个class的内容,第一行89列可以看出,0x15 = 21,该文件中的常量池表项有21-1=20项(常量池0位空缺项,不存放实际内容)。数量之后就是常量池实际内容。

 

四、Class的访问标记


 常量池后紧跟的是访问标记,用2字节表示,用于表明类的访问信息,如publicfinalabstract

Access Flag的标记位和含义

 

标记名称

数值

描述

ACC_PUBLIC

0x0001

表示public

ACC_FINAL

0x0010

final

ACC_SUPER

0x0020

使用增强的方法调用父类方法

ACC_INTERFACE

0x0200

接口

ACC_ABSTRACT

0x0400

抽象类

ACC_SYNTHETIC

0x1000

由编译器产生的类,没有源码对应

ACC_ANNOTATION

0x2000

注释

ACC_ENUM

0x4000

枚举

(表中数值为了容易描述而取的特殊值)

比如,0x0021表示该类为publicACC_SUPER标记置为1

写一个简单的类

编译后部分class文件如下

0x0421表明该类是抽象类,且为publicACC_SUPER置为1(一般都会为1,继承object类)

 

五、当前类、父类和接口


 在访问标记后,会指定该类的类别、父类类别和实现的接口,格式如下:

u2    this_class;

u2    super_class;

u2    interfaces_count;

u2    interfaces[interfaces_count];

其中this_classsuper_class都是2字节无符号整数,指向常量池一个CONSTANT_Class,表示当前类型和父类,由于可以继承多个接口,因此,需要用数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class

 

继续使用该class,其中00 02 00 03表明的是本类和父类在常量池的索引,后面的 00 00 表明继承接口数量为0,因此interfaces数组没有数据

 

 六、Class文件的字段


 接口描述后,会有类的字段信息

u2            fields_count

field_info   fields[fields_count]

字段数量fields_count是一个2字节无符号整数,field_info是字段具体信息,结构如下:

field_info{
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

字段Access Flag的标记位及含义

标记名称

取值

描述

ACC_PUBLIC

0x0001

表示public字段

ACC_PRIVATE

0x0002

表示private字段

ACC_PROTECTED

0x0004

表示protected字段

ACC_STATIC

0x0008

表示静态字段

ACC_FINAL

0x0010

表示final字段

ACC_VOLATILE

0x0040

表示volatile字段

ACC_TRANSIENT

0x0080

表示瞬时字段,在持久化读写时忽略该字段

ACC_SYNTHETIC

0x1000

由编译器产生的方法,没有源码对应

ACC_ENUM

0x4000

枚举

 

 

 

继续使用上面的例子,从offset0000027210列开始,00 01表明的是字段数量,该类中数量为1,后续00 19表明这个字段是public static final字段,接下来00 04为常量池索引,表示字段名称,常量池第四个为a,即字段名称为a00 05为字段类型描述,常量池第5java/lang/String,表示为String类型,接着是属性数量,00 01表示字段存在一个属性, 后面的00 06为属性名,常量池第6项为ConstantValue,表示该属性为常量属性。

常量属性的结构为

ConstantValue_attribute{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}

之后连续4字节为属性剩余长度,00 00 00 02,表示从0x00000002之后的2字节为属性全部内容,00 07即常量池第7项,得到常量CONSTANT_String,值为空。所以分析出该常量字段为public static final String a = “”;

 

七、class文件的方法基本结构


在字段之后,就是类的方法信息,它由两部分组成

u2 methods_count;
method_info methods[methods_count];

每个method_info表示一个方法,结构如下

method_info{
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

方法访问标记取值

标记名称

作用

ACC_PUBLIC

0x0001

public

ACC_PRIVATE

0x0002

private

ACC_PROTECTED

0x0004

protected

ACC_STATIC

0x0008

静态方法

ACC_FINAL

0x0010

final方法不可被重载重写

ACC_SYNCHRONIZED

0x0020

同步方法

ACC_BRIDGE

0x0040

由编译器产生的桥接方法

ACC_VARARGS

0x0080

可变参数方法

ACC_NATIVE

0x0100

native

ACC_ABSTRACT

0x0400

抽象方法

ACC_STRICT

0x0800

浮点模式为FP-strict

ACC_SYNTHETIC

0x1000

编译器产生的方法,没有源码对应

 

访问标记name_index表示方法名称,是指向常量池的索引

descriptor_index为方法描述符,也是指向常量池的索引,表示方法的签名(参数、返回值等)

后面的attributes_countattribute_info表示属性数量及描述

attribute_info{
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

常用属性

属性

作用

ConstantValue

字段常量

Code

表示方法的字节码

StackMapTable

Code属性的描述属性,用于字节码变量类型验证

Exceptions

方法的异常信息

SourceFile

类文件的属性,表示生成这个类的源码

LineNumberTable

Code属性的描述属性,描述行号和字节码的对应关系

LocalVariableTable

Code属性的描述属性,描述函数局部变量表

BootstrapMethods

类文件的描述属性,存放类的引导方法,用于invokeDynamic

StackMapTable

Code属性的描述属性,用于字节码类型校验

 

Code属性较为复杂,整理一下作用与结构成下图

其中对于Class还有InnerClasses属性和Deprecated属性,比较少用到这里就不多说。

 

八、Class文件总结


 Class文件是非常庞大的,这里我只摘抄一些比较重要的点做一些笔记,后续随着Java平台的发展,Class文件也会有很多补充,但是基本的结构和文件格式应该是不会做重大调整的。从Java虚拟机的角度看,通过Class文件可以让更多的计算机语言支持Java虚拟机平台,Class文件不仅仅是Java虚拟机的执行入口,更是Java生态圈的基础和核心

 

 

posted @ 2019-10-11 14:41  一秋复一秋  阅读(591)  评论(0编辑  收藏  举报