《深入理解Java虚拟机》读书笔记2-class文件结构
class文件结构
Class文件内容可以分为两种数据类型:无符号数和表。其中无符号数包括u1,u2,u3,u4,分别代表1个字节,2个字节,3个字节和4个字节。无符号数可以表示数字、UTF8编码的字符串。表是由多个无符号数或者其他表构成的数据结构,以_info结尾。可以看出Class文件的基础单位是8位的字节,而当遇到多个字节构成数据项时,是按照高位在前的顺序排列(Big-Endian)。
Class文件的组成数据项按先后顺序如下表所示:
类型 |
名称 |
数量 |
u4 |
magic魔数 |
1 |
u2 |
minor_version次版本号 |
1 |
u2 |
major_version主版本号 |
1 |
u2 |
Constant_pool_count常量池计数 |
1 |
cp_info |
constant常量池 |
Constant_pool_count |
u2 |
Access_flag访问标志 |
1 |
u2 |
This_class类索引 |
1 |
u2 |
Super_class父类索引 |
1 |
u2 |
Interfaces_count接口索引计数 |
1 |
u2 |
interfaces接口池 |
Interfaces_count |
u2 |
Fields_count 字段表计数 |
1 |
field_info |
Fields |
Fields_count |
u2 |
Attributes_count 字段属性表计数 |
1 |
attribute_info |
Attributes |
Attributes_count |
u2 |
Methods_count方法表计数 |
1 |
method_info |
methods方法池 |
Methods_count |
u2 |
Attributes_count 方法属性表计数 |
1 |
attribute_info |
Attributes |
Attributes_count |
u2 |
Attributes_count 类属性表计数 |
1 |
attribute_info |
Attributes |
Attributes_count |
Class文件按照上表的数据项顺序紧凑地排序,无分隔符。(其中属性表是可能存在,若无相关属性时则不存在属性表集合)
可以使用jdk自带的分析class文件字节码工具javap将class文件转化为字符形式,如javap –verbose Test.class。如:
1 magic魔数
魔数的作用是标识文件格式,之所以不用文件后缀名是因为文件后缀名容易被更改。Class文件的前四个字节即魔数,十六进制固定为:0xCAFEBABE(咖啡宝贝)。
2 version版本号
class文件中紧接魔数的是2字节的次版本号和 2字节的主版本号。java主版本号从45开始,jdk1.1之后的每个大版本依此加1(如jdk2为46,jdk7为51),次版本号为0~65535。Java虚拟机严格要求只能执行不能超过其版本的class文件,比如jdk1.1只能执行45.0~45.65535的class文件,jdk7只能支持45.0~51.65535的class文件。
3 constants_pool常量
Class文件中,相同数据项的集合都是以一个容量计数值,加上该计数值个数据项组成。比如常量池,首先是一个constants_pool_count,之后是constants_pool_count个constants。
常量池容量计数值count是一个u2无符号数,代表接下来真正的常量有count-1个,之这是因为规定常量池索引从1开始,不分配索引0则是为了满足某些指向常量池的数据在特定情况下不需要引用任何一个常量的情况。
常量池是class文件的资源库,存放了字面量、类和接口全限定名、字段名称和描述符、方法名称和描述符。总共有14种类型,每种类型都有不同的表结构。但统一的时每种表结构都是以一个u1类型的tag标志位开始,代表该常量的类型。具体每种常量结构如下表所示:
4 access_flag访问标志
接下来是一个u2类型的访问标志位,该位有16位,目前只使用了8位,每一位根据0、1值代表当前类的不同访问信息,剩余8位一律为0。具体标志位及其含义如下图所示:
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
是否位public类型 |
ACC_FINAL |
0x0010 |
是否被申明为final,只有类可设置 |
ACC_SUPER |
0x0020 |
是否允许使用invokespecial字节码指令的新语义(该指令语义在jkd1.0.2发生过改变) |
ACC_INTERFACE |
0x0200 |
是否是接口 |
ACC_ABSTRACT |
0x0400 |
是否为abstract,对于接口和抽象类为真 |
ACC_SYNTHETIC |
0x1000 |
是否并非由用户代码产生 |
ACC_ANNOTATION |
0x2000 |
是否是注解 |
ACC_ENUM |
0x400 |
是否是枚举 |
5 this_class类索引
接下来是一个u2类型的索引,指向常量池中代表这个类全限定名的CONSTANT_Class_info数据项。(全限定名就是把类全名中的.换为/)
6 super_class父类索引
父类索引类型同类索引,也是CONSTANT_Class_info。Java中除Object类以外,所有类都有父类,即super_class索引项不为0,而Object类的super_class索引项是0代表为空。
7 interfaces 接口索引集合
接口索引集合先有一个u2类型的计数值,代表后续有多少个接口索引,接口索引也是u2类型的常量池。接口顺序是完全按照类implements(接口extends)语句之后的顺序。
8 fields字段表集合
字段表集合同样由一个fields_count字段表计数值加fields_count个字段表组成。Fields_count还是u2类型。字段表结构如下:
类型 |
名称 |
数量 |
u2 |
Access_flags访问标志 |
1 |
u2 |
Name_index字段名索引 |
1 |
u2 |
Descriptor_index 描述符索引 |
1 |
u2 |
Attributes_count 属性表计数值 |
1 |
Attribute_info |
Attributes属性表 |
Attributes_count |
- l 首先是类似于类访问标志的一个u2类型的字段访问标志,目前有9位在用标志位,具体含义如下图所示:
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
是否public |
ACC_PRIVATE |
0x0002 |
是否private |
ACC_PROTECTED |
0x0004 |
是否protected |
ACC_STATIC |
0x0008 |
是否static |
ACC_FINAL |
0x0010 |
是否final |
ACC_VOLATILE |
0x0040 |
是否volatile |
ACC_SYNTHETIC |
0x1000 |
是否由编译器产生 |
ACC_ENUM |
0x4000 |
是否enum |
- l 之后是字段简单名称,即没有类型的字段名。
- l 描述符是指字段的类型、方法的参数和返回值。规定对于基础类型以及代表返回值的void使用一个大写字符标识(boolean类型用Z),对象类型用L加对象的全限定名,对于数字则使用[加数组元素类型标识。所以字段int m 的描述符为I。对于方法的描述符则规定按参数在前,返回类型在后的顺序,参数使用()包含起来。所以方法int indexOf(char[] source,String str)描述符为([CS])I。
- l 属性表集合,由属性表计数值和属性表计数值个属性表组成,属性表记录内容为字段其他属性,比如final static int m=123,就会有一个ConstantValue的属性记录常量值。属性表内容见之后的属性表集合讲解。
此外,需要注意字段表不会包含父类的字段,但有可能列车原本java代码中不存在的字段,比如内部类会添加访问外部类的实例字段。
9 methods方法表集合
方法表集合记录了类的方法集合,结构同字段表集合类似:Methods_count+methods。其中方法表的结构表示也相同,只是方法的access_flag访问标识如下表所示:
标志名称 |
标志值 |
含义 |
ACC_PUBLIC |
0x0001 |
是否public |
ACC_PRIVATE |
0x0002 |
是否private |
ACC_PROTECTED |
0x0004 |
是否protected |
ACC_STATIC |
0x0008 |
是否static |
ACC_FINAL |
0x0010 |
是否final |
ACC_SYNCHRONIZED |
0x0020 |
是否为synchronized |
ACC_BRIDGE |
0x0040 |
是否为编译器产生的桥接方法 |
ACC_VARARGS |
0x0080 |
是否接受不定参数 |
ACC_NATIVE |
0x0100 |
是否为native |
ACC_ABSTRACT |
0x0400 |
是否为abstract |
ACC_STRICTFP |
0x0800 |
是否为strictfp |
ACC_SYNTHETIC |
0x1000 |
是否由编译器产生 |
注意当子类没有重写父类方法时,不会出现父类的方法表。同时也有可能产生原java代码中没有的方法,如类初始化方法<clinit>,实例构造器<init>。方法也有属性表集合。
有关属性表集合见下一篇吧~~