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

前言

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

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

397826a0ca39314db25e9a9ef960b282.png

在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

d109313e7261d0d072d28ebaf1aa847e.png

这是从我项目中随机取的一个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

10b6e5d0d868fb4bc206ccd53d18d21f.png

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

四、Class的访问标记

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

类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表示该类为public且ACC_SUPER标记置为1

写一个简单的类

7c8d816072defa64e82282bee62c5393.png

编译后部分class文件如下

80412673100f9610b202d27f011845ca.png

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

五、当前类、父类和接口

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

u2 this_class;

u2 super_class;

u2 interfaces_count;

u2 interfaces[interfaces_count];

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

2c4d5703b958804b21fbdf46dc8ee636.png

继续使用该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

枚举

632f6a79565f8e66b4a35482c314a5ee.png

继续使用上面的例子,从offset的00000272第10列开始,00 01表明的是字段数量,该类中数量为1,后续00 19表明这个字段是public static final字段,接下来00 04为常量池索引,表示字段名称,常量池第四个为a,即字段名称为a,00 05为字段类型描述,常量池第5个java/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_count和attribute_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属性较为复杂,整理一下作用与结构成下图

0d1b0f33b41bf606681ac5855f079fa3.png

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

八、Class文件总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值