class 文件结构
- 一、class文件
- 二、魔数(Magic Version)
- 三、class 文件版本号
- 四、常量池
- CONSTANT_POOL_COUNT(常量个数)
- CONSTANT_POOL(常量池表)
- 五、修饰符(access_flags)
- 六、类的名称(this_class)
- 七、父类的名称(super_class)
- 八、接口的数量(interfaces_count)
- 九、接口(interfaces)
- 十、变量的数量(fields_count)
- 十一、变量(fields)
- 十二、方法的数量(methods_count)
- 十三、方法(methods)
- 十四、附加属性(attributes和attributes_count)
一、class文件
Class文件是一组以8位字节为基础单位的二进制流,包含多个数据项目(数据项目的顺序,占用的字节数均由规范定义),各个数据项目严格按照顺序紧凑的排列在Class文件中,不包含任何分隔符,使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙。当遇到需要占用超过8位字节以上空间的数据项目时,会按照高位在前的方式分割为多个8位字节进行存储。
Java虚拟机规范规定的,class文件格式采用的类似C语言的结构体的伪结构来存储的,这种结构只有两种数据类型:
- 无符号数:属于基本数据类型,主要用于描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值。数据类型 U1 U2 U4 U8 也只是逻辑上的区分。
- u1 — 表示一个字节
- u2 — 表示两个字节
- u4 — 表示四个字节
- u8 — 表示八个字节
- 表:由多个无符号数或者其他表作为数据项构成的复合数据类型。所有的表都习惯以_info结尾 表主要用于描述有层次关系的复合结构数据。 比如方法、字段需要注意的是class文件没有分隔符,所以每个二进制数据类型都是严格定义的具体的顺序如下:
二、魔数(Magic Version)
- 每一个class文件的头4个字节被称为魔数 magicNumber
- 唯一做你哥是用于确定这个文件是否为一个能被虚拟机接受的class文件
- Class文件魔数值为0xCAFEBABE 如果以个文件不是以CAFEBABE开头,那么它就肯定不是java的class文件。
那么它也是java.class的识别魔数。
很多的文件存储标准中都使用魔数来识别文件的身份。 譬如图片格式.gif 或 jpeg等在文件的头部都存有魔数,使用魔数而不是文件的扩展名称来判断 ,这种情况是处于安全的考虑。
三、class 文件版本号
紧挨着魔数的4个字节表示class的文件的版本号 版本号:
1. 次版本号
minor_version 前2个字节用于表示次版本号
例如:00 00
2. 主版本号
major_version 后2个字节用于表示主版本号
例如: 00 34
这个版本号随着jdk版本的不同而表示不同版本的范围。Java的版本号是从45开始的,如果class的版本号超过虚拟机的版本就会被拒绝执行。
- JDK1.2 ----0X002E 46
- JDK1.3 ----0X002F 47
- JDK1.4 ----0X0030 48
- JDK1.5 ----0X0031 49
- JDK1.6 ----0X0032 50
- JDK1.7 ----0X0033 51
- JDK1.8 ----0X0034 52
四、常量池
CONSTANT_POOL_COUNT和CONSTANT_POOL,紧跟着魔数与版本号之后的是常量池入口,常量池简单理解为class文件的资源库。
- 它是class文件结构中与其他项目关联最多的数据类型
- 是占用class文件空间最大的数据项目之一
- 是在文件中第一个出现的表类型数据项目。
CONSTANT_POOL_COUNT(常量个数)
常量池的数量是不固定,所以在常量池的入口需要放置一个u2类型的数据,代表常量池的计数值CONSTANT_POOL_COUNT。
CONSTANT_POOL_COUNT 从1开始计数的。 class文件结构中只有常量池的容量计数是从1开始的。第0项腾出来满足后面某些指向常量池的索引值的数据,在特定的情况下需要表达“不引用任何一个常量池项目” 把索引值的第0项留给JVM自己用。
CONSTANT_POOL是没有索引值为0的入口的,但是在CONSTANT_POOL_COUNT缺失的第0项也是要被计算在内的。比如CONSTANT_POOL 中有14项 那么CONSTANT_POOL_COUNT的数值就是15
CONSTANT_POOL(常量池表)
常量池中主要存放两大类常量:
1.字面量: 比较接近java语言层面的常量的概念 比如 字符串 被final关键字声明的常量值。
2.符号引用: 属于编译原理方面的概念 包括三项:
- 类和接口的全名
- 字段的名称和描述符
- 方法的名称和描述符
在加载class文件的时候 是进行动态连接的。在class文件中不会保存各个方法和字段的最终内存布局信息。(需要经过转换) 当虚拟机运行时 需要从常量池获得对应的符号引用,再在类创建时或者运行时解析并翻译到具体的内存地址中。
CONSTANT_POOL 表示的是类型数据集合,在该常量池中,每一项常量都是一个表 共有14种 -----JDK1.7版本,这14种结构的表都是不相同的结构数据。14个表都有一共同的特点,都是由u1的标志位开始的,可以通过这个标志位来判断这个常量属于哪种常量的类型。
在如下例子中:
package com.openlab;
public class JVMTest01 {
public static void main(String[] args) {
System.out.println(1);
}
}
如下图所示常量池:
CONSTANT_POOL_COUNT 占2个字节 本例中为0x20 转换成十进制为32 说明常量池中有31个常量 ----从1开始计数 其他集合类型均从0开始。 索引值为1-31 第0项常量具有特殊意义。
五、修饰符(access_flags)
用于表示对该类或接口的访问权限以及该类或接口的属性
六、类的名称(this_class)
该this_class 项目的值必须是constant_pool表中的有效索引,该constant_pool索引处的条目必须是表示此文件定义的类或接口 CONSTANT_Class_info 结构 class。
七、父类的名称(super_class)
必须是constant_pool表中的有效索引, 如果super_class的值不为0 则constant_pool中的条目必须为CONSTANT_Class_info 结构 这个结构表示此类的文件定义的类的直接超类。直接超类不能在其classfile结构的access_flag项中设置 ACC_FINAL 标志。
其实要描述的意思就是说 如果superclass指代的超类,那么它就不能被final修饰。
八、接口的数量(interfaces_count)
接口索引计数器,占2字节。如果该类没有实现任何接口,则该计数器值为0,并且后面的接口的索引集合将不占用任何字节。
九、接口(interfaces)
接口索引集合,一组u2类型数据的集合。用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中。
十、变量的数量(fields_count)
表数据,也就是测试类中只包含一个变量(不算方法内部变量)。
十一、变量(fields)
字段表集合,一组字段表类型数据的集合。字段表用于描述接口或类中声明的变量,包括类级别(static)和实例级别变量,不包括在方法内部声明的变量。
在Java中一般通过如下几项描述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示,字段表格式如下表所示:
类型 | 名称 | 数量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
与method属性值相同。
通过举一个实例,来具体了解变量:
第9个常量池LineNumberTable 是在后面要使用的常量的名字,在code当中被引用到。
第10号常量池LocalVariableTable是在后面要使用的常量的名字,在code当中被引用到。
十二、方法的数量(methods_count)
方法表计数器,即方法表集合中的方法表数据个数。占2字节,其值为0x0002,即测试类中有2个方法
十三、方法(methods)
Method属性包含三个字段值:
类型 | 名称 | 数量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
与fields属性值相同。
- 参数列表(参数类型) 后——返回值
- void m() 等同于 ()V
- String toString() ——> ()Ljava/lang/String;
- Long pos(int[] arr1,int arr2,long length) ——> ([IIJ)J
- [ 一维数组
- [[ 表示二维数组
因为在我们案例中没有声明接口和成员变量,所以对应没有展开选项的。
1.init 无参的构造方法 —默认提供的构造方法
2.主函数main
方法中的access_flag:
十四、附加属性(attributes和attributes_count)
附加属性:方法中的附加属性就是code,那么code在这里是比较重要的概念,code是具体代码的实现,当我们写入方法的时候,它能够把方法中代码转化为一条条指令。
在官方文档中,我们可以看到很多十六进制的数字,那么它们对应方法中code的内容实现。也可以鼠标右键点击:
从本地变量表中第0项放入到栈空间中。
附加属性中有的代码中存在内容,有的不存在内容;
- 既有预定义的属性,也可以自定义 java虚拟机会自动忽略它不认识属性
- Code 表示的是方法表 方法表能够编译成字节码指令,还存放了操作数栈和局部变量的信息。
- u2 attribute_name_index 指向常量池中的CONSTANT_UTF8_info 存放的当前属性的名字就是code。
- u4 attribute_length 表示的code属性的长度 (不包括前6个字节)。
- u2 max_stack 指定当前方法被执行引擎执行的时候,在栈帧中需要分配的操作数栈的大小
- u2 max_locals 指定当前方法被执行引擎执行的时候,在栈帧中需要分配的局部变量表的大小
- u4 code_length 指定方法字节码的长度, class文件中每条字节码都占用一个字节
- u1 code 存放字节码指令本身,它的长度是code_length个字节。
- u2 exception_table_length指定异常表的大小
- Exception_table异常表 作用对try-catch-finally的描述,可以把它看成是一个数组。每一个数组项都是一个exception_info结构, 一般来说每个catch块对应一个exception_info,编译器也可能会对当前的方法生成一些exception_info。
- U2 start_pc 是从字节码code属性中的一部分,起始处到当前异常处理器的起始处的偏移量。
- u2 end_pc 从字节码起始处到当前异常处理器 末尾的偏移量
- u2 handler_pc 是指当前异常处理器用于处理异常(即catch块)的第一条指令相对于字节码开始处的偏移量。
- u2 catch_type 是常量池的索引 指向的是常量池CONSTANT-Class_info 数据项,描述了catch块中的异常类型的信息。这个类必须是java.lang.Throwable的或者是它的子类。
- 总结:
如果偏移量从start_pc到end_pc之间,如果字节码出现了catch_type所描述的异常,那么就跳转偏移量到handler_pc的字节码中去执行。如果catch_type 为0 就代表不引用任何常量池的信息,那么这个exception_info 用于实现finally的子句。
- u2 attribute_count 表示的是code属性中存在的其他属性的个数。会出现在 class中的属性,在field属性也有,在method属性也有。
Attributes 可以把它看成是个数组,里面存放了code属性的其他属性:
- ConstantValue ---- 字段表 final关键字自定义的常量值
- Deprecated — 类 方法表 字段表
- Exception — 异常表
- EnclosingMethod — 类文件 局部类或匿名类的外部封装方法
- InnerClass — 类文件 内部类列表
可选属性:
- LineNumberTable 源码的行号和字节码行号的对应关系,可以把这个属性看成是一个数组,
- 数组中的每项LineNumberinfo结构描述了一条字节码和源码行号的对应关系
- LocalVariableTable 建立了方法中的局部变量与源代码中的局部变量的对应关系。