java语言之所以能够跨平台是因为java代码被编译成为了字节码,也就是class类。本职上class文件也就是二进制,Java虚拟机指令集和符号表以及若干其他辅助信息。这种二进制文件和平台无关,只要是在java虚拟机上都可以运行。
1.Class文件格式
class文件是以8字节为单位的的二进制流文件, 各个数据项按顺序紧密排列。每项数据都有它的固定长度, 数据项的不同长度分别用u1, u2, u4, u8表示, 分别表示一种数据项在class文件中占据一个字节, 两个字节, 4个字节和8个字节。class文件数据项如下:
2 magic
class文件头的四个字节是class文件的魔数, 这个魔数是class文件的标志,他是一个固定的值: 0XCAFEBABE 。 JVM通过这个识别是不是class文件。
3 minor_version 和 major_version
次版本号和主版本号,高版本的JVM可以识别低版本的class文件,反之不行。
4 constant_pool_count
常量池中常量的数量
5 constant_pool
常量池中存放了文字字符串, 常量值, 当前类的类名, 字段名, 方法名, 各个字段和方法的描述符, 对当前类的字段和方法的引用信息, 当前类中对其他类的引用信息等等。
常量池中各个数据项通过索引来访问, 不过常量池中的第一项的索引为1, 而不为0, 如果class文件中的其他地方引用了索引为0的常量池项, 就说明它不引用任何常量池项。同样,常量池中的每一种数据项也有自己的类型。
常量池中的每个数据项名字是数据项类型+“_info”。
常量池中的内容:
5.1 类的全限定名
常量池中的类名是java中的类的全路径名,然后把点换成/。
5.2 字段和方法描述符
我们知道类的字段都有类型,方法的参数和返回值也有类型,这些类型在class文件中怎么描述的。
以上是基本类型,引用在class文件中用 “L” + 类型的全限定名 + “;”。数组用若干个“[” + 数组中元素类型的对应字符串 。
所以字段的描述符就是上述的类型描述符。方法的描述符包含参数和返回值:(参数1类型 参数2类型 参数3类型 …)返回值类型。
这里说下字段和方法的描述符合字段和方法名字是两个东西。还有个比较特殊就是类额构造方法和静态初始化方法。类的构造方法的方法名使用字符串 **<init>**表示, 而初始化方法包括静态初始化的方法名使用字符串 **<**clinit> 表示。
以下才是真正开始按照前面常量池类型图来介绍
5.3 CONSTANT_Utf8_info
CONSTANT_Utf8_info是CONSTANT_Utf8类型的常量池数据项, 它存储的是一个常量字符串。
如图包含三个部分:第一个tag为1,标识这个是个CONSTANT_Utf8类型,占一个字节,第二个部分标识常量的字节数长度,占两个字节,最后一个就是内容。上面说的类名、方法描述符等都存在这个里面。
5.4 CONSTANT_NameAndType
它主要用来描述方法和字段信息。Name就是字段和方法的名称,Type是字段和方法的描述符 。
如图包含三个部分:第一个tag为12,标识什么类型常量,占一个字节。第二个是名字的索引,占两个字节,第三个描述符索引,占两个字节。
5.5 CONSTANT_Integer_info
它用来描述int型数据的值。
如图包含两个部分:第一个tag为3,标识什么类型常量,占一个字节。第二个是四个字节,存储值。
5.6 CONSTANT_Float_info
它用来描述float型数据的值。
如图包含两个部分:第一个tag为标识什么类型常量,占一个字节。第二个是四个字节,存储值。
5.7 CONSTANT_Long_info
它用来描述long型数据的值。
如图包含两个部分:第一个tag为标识什么类型常量,占一个字节。第二个是8个字节,存储值。
5.8 CONSTANT_Double_info
它用来描述double型数据的值。
如图包含两个部分:第一个tag为标识什么类型常量,占一个字节。第二个是8个字节,存储值。
5.9 CONSTANT_String_info
如图包含两个部分:第一个tag为标识什么类型常量,占一个字节。第二个是2个字节,是内容的索引。
5.10 CONSTANT_Class_info
描述类或者接口的信息。
如图包含两个部分:第一个tag为标识什么类型常量,占一个字节。第二个是2个字节,是类或者接口的索引。
5.11 CONSTANT_Fieldref_info
表示对一个字段的符号引用, 可以是对本类中也可以是其他类, 可以是对成员变量, 也可以是静态变量。
它包含三个部分:第一个部分tag为标识什么类型常量,占一个字节。第二个部分是class_index,指向一个CONSTANT_Class_info。第三个是NameAndType的索引,指向一个CONSTANT_NameAndType_info。
5.12 CONSTANT_Methodref_info
表示对一个方法的符号引用, 可以是对本类中也可以是其他类, 可以是对成员方法, 也可以是静态方法。
它包含三个部分:第一个部分tag为标识什么类型常量,占一个字节。第二个部分是class_index,指向一个CONSTANT_Class_info。第三个是NameAndType的索引,指向一个CONSTANT_NameAndType_info。
5.13 CONSTANT_InterfaceMethodref_info
和上一个类似,这里不描述了
6 access_flags
当前类(或者接口)的访问修饰符
7 this_class
描述的是当前类。 它的两个字节的数据是对常量池中的一个CONSTANT_Class_info数据项的一个索引。
8 super_class
描述的是当前类的超类。 它的两个字节的数据是对常量池中的一个CONSTANT_Class_info数据项的一个索引。
因为常量池中的数据项是从1开始的,如果一个类的class文件中的super_class为0,那么就代表该类直接继承Object类。
9 interfaces_count
前类所实现的接口或者所继承的接口的数量。 只统计直接实现或者继承的接口。
10 interfaces
在interfaces_count后面是interfaces,每个数组项是一个索引, 指向常量池中的一个CONSTANT_Class_info。
11 attributes_count
attributes_count描述的是当前的class文件中属性数量。注意这里属性不是指字段和方法。将这个属性提前介绍是因为后面介绍的其他数据项会用到。
12 attributes
attributes紧跟着attributes_count后面,每个都是一个attribute_info,表示一个属性。属性不仅仅可以出现在顶层的Class文件中, 也会field_info中描述特定字段, 还可以出现在method_info中描述方法。
其中第一部分占两个字节,代表当前属性的名字,指向一个CONSTANT_Utf8_info。第二个部分是attribute_length,属性的长度。第三部分是属性的值。
(1)SourceFile属性
描述了该类是从哪个源文件中编译来的,顶层class文件中的属性。
(2)InnerClasses属性
这个属性比较复杂,不多讲,看图了解下,顶层class文件中的属性。
(3)Synthetic属性
表示这个字段, 方法或类不是有用户代码生成的。
(4)ConstantValue属性
出现在class文件中的field_info中,每个field_info中最多只能出现一个ConstantValue属性,而且必须是静态字段。
静态变量初始化的方式有两种, 一种就是ConstantValue属性, 另一种就是静态初始化方法。如果虚拟机决定使用ConstantValue属性为静态变量赋值, 那么为赋值动作, 必须位于执行方法之前。
此外, 只有基本数据类型或String类型的静态变量才可以存在ConstantValue属性。
attribute_name_index是属性名字,attribute_length是属性长度。constantvalue_index是常量池的索引。这时候有一个注意地方,在我们常量池中没有byte, short, char,boolean类型,所以也就是我们常说的JVM在执行时当做int类型处理。
(5)Deprecated属性
Deprecated属性可以存在于filed_info中, method_info中和顶层的ClassFile中, 分别表示这个字段, 方法或类已经过时。
(6)Code属性
code属性是方法的属性。 存放的是方法的字节码指令, 除此之外还存放了和操作数栈,局部变量相关的信息。 所有不是抽象的方法,都必须在method_info中的attributes中有一个Code属性。
attribute_name_index:存放的是常量池索引,实际就是当前属性的名字 “Code”。
attribute_length:属性的长度
max_stack:当前方法被执行时,在栈帧中需要分配的操作数栈的大小。
max_locals:当前方法被执行时, 在栈帧中需要分配的局部表量表的大小。需要注意的是由于局部变量表可以重用(不清楚的可以看下我JVM前面的文章),这个数字并不是局部变量的个数。 方法中的局部变量包括方法的参数,方法的默认参数this,方法体中定义的变量,catch语句中的异常对象。
code_length:指定该方法的字节码的长度。
code:存放字节码指令本身, 它的长度是code_length个字节。
exception_table_length:指定异常表的大小
exception_table:异常表是对方法体中try-catch_finally的描述。 exception_table是exception_info结构。
- start_pc是从字节码(Code属性中的code部分)起始处到当前异常处理器起始处的偏移量。
- end_pc是从字节码起始处到当前异常处理器末尾的偏移量
- handler_pc是指当前异常处理器用来处理异常(即catch块)的第一条指令相对于字节码开始处的偏移量
- catch_type是一个常量池索引,描述了catch块中的异常的类型信息
attributes_count 和attributes这两个不用说了,从这可以看出Code属性里还可以包含其他属性。
(7)LineNumberTable属性
LineNumberTable属性存在于Code属性中, 它建立了字节码偏移量到源代码行号之间的联系。 这个属性是可选的, 编译器可以选择不生成该属性。
start_pc是这个line_number_info 描述的字节码指令的偏移量,line_number是对应的源码中的行号
(9)LocalVariableTable属性
LocalVariableTable 属性建立了方法中的局部变量与源代码中的局部变量之间的对应关系。 这个属性存在于Code属性中。 这个属性是可选的, 编译器可以选择不生成这个属性。
start_pc:当前local_variable_info所对应的局部变量的作用域的起始字节码偏移量;
length:当前local_variable_info所对应的局部变量的作用域的大小。 也就是从字节码偏移量start_pc 到start_pc+length就是当前局部变量的作用域范围;
name_index:当前局部变量的变量名;
descriptor_index:当前局部变量的描述符;
index:当前局部变量在栈帧中局部变量表中的位置。
(10)LocalVariableTable属性
Exceptions属性不是存在于Code属性中的,它存在于method_info中的attributes中。这个属性描述的是方法声明的可能会抛出的异常。
attribute_name_index:属性名索引
attribute_length:属性长度
number_of_exceptions:方法要抛出的异常个数。
exceptions_index_table:指向常量池CONSTANT_Class_info。
13 fields_count
fields_count描述的是当前的类中字段的个数, 不包括从父类继承的字段。此外要说明的是, 编译器可能会自动生成字段,导致class文件中的字段的数量可能多于源文件中字段的数量。 比如编译器会为内部类增加一个字段,这个字段是指向外围类的对象的引用。
14 fields
field_info结构如下:
(1)access_flags
访问标志
(2)name_index
字段名称索引
(3)descriptor_index
描述索引
(4)attributes_count和attributes
不多说,前面已经介绍了
15 methods_count
methods_count描述的是当前的类中方法的个数, 不包括从父类继承的方法。此外要说明的是, 编译器可能会自动生成方法,导致class文件中的方法的数量可能多于源文件中方法的数量。 比如无参构造器,比如当前类或接口中定义了静态变量, 并且使用初始化表达式为其赋值, 或者定义了static静态代码块, 那么编译器在编译的时候会默认增加一个静态初始化方法 。
16 methods
methods_info结构如下:
(1)access_flags
访问标志
(2)name_index
字段名称索引
(3)descriptor_index
描述索引
(4)attributes_count和attributes
不多说,前面已经介绍了