本文为《深入理解Java虚拟机JVM高级特效与最佳实践(第三版)》一书的摘要总结
Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:“无符号数”和“表”:
JDK1.2到JDK11都没有用,全部固定为0。到了JDK12,次本号用于标识“技术预览版”功能特性的支持。如果Class文件中使用了预览功能,则必须将此版本标识为:65535。
从45开始,每个JDK大版本发布,主版本号就向上加1。JDK1.01.1使用了45.045.3的版本号,JDK1.2使用的版本号46,高版本的JDK能向下兼容以前版本的Class文件,但是不能运行以后版本的Class文件。
JVM规范文档
从1开始计数。
常量池中的每一个常量都是一个表,截止到JDK13,常量表中分别有17种不同类型的常量。这17类表都有一个共同的特点:表结构的起始的第一位是一个u1类型的标志位,代表这当前常量属于那种类型。
常量池中的常量类型:
代表类或者接口的符号引用。
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
UTF-8编码的字符串。
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
CLass 文件中,方法、字段等都需要引用CONSTANT_UTF8_info型类来描述名称,所以CONSTANT_UTF8_info类型常量的最大长度就是Java中方法、字段名的最大长度(2个字节16位,length最大为216,所以方法、字段名最长为216Byte=64KB)
还有很多,后面再慢慢补充。
常量池结束后后紧跟着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层出的访问信息。
ACC_SUPER:是否允许使用invokespecial字节码指令的新语义,invokespecial指令的语义在JDK1.0.2发生过改变,所以JDK1.0.2之后编译出来的Class的这个标志都是真。
如 public abstract class{}
,他的访问标志位:
0x0001|0x0020|0x0400 = 0x0421。
类索引是一个u2类型的数据,是一个指向常量池中CONSTANT_Utf8_info类型的索引,用于确定这个类的全限定名。
与类索引相似,是一个u2类型的数据,是一个指向常量池中CONSTANT_Utf8_info类型的索引,用于确定这个类的父类的类全限定名。
因为Java不允许多重继承,所以父类索引只能有一个,除了java.lang.Object外,其他类的父类索引都不为0。
接口索引集合入口的第一项是u2类型的接口计数器,标识索引表的容量。如果类没有实现任何接口,那么计数器为0,后面的接口索引列表不占空间。如果类实现了接口,那么按照源码中implement
后接口的顺序从左至右排列接口索引,每一项索引是一个u2类型的数据,指向常量表中一个CONSTANT_Utf8_info,用于确定接口的全限定名。
字段表用于描述接口或者类中声明的变量。这里的字段指类级变量和实例级变量,但不包括方法内部声明的局部变量。
字段表结构:
field_info {
u2 access_flags;
u2 name_index; //字段的简单名称
u2 descriptor_index; //字段描述符
u2 attributes_count;
attribute_info attributes[attributes_count];
}
###访问标志
Java语言中描述一个字段可以包含的信息:
上述描述信息的访问标志:
字段的简单名称:没有类型修饰的字段名称
字段的全限定名称:所属类的全限定名称(分隔符从’.‘换成’/’) + ‘.’ + 简单名称
字段描述符:简单名称 + ‘:’ + 字段类型,其中字段类型标识规则如下:
见最后的属性表结合一节
Class文件存储格式中对方法的描述与对字段的描述采用了几乎一致的方式。它的结构如下:
method_info {
u2 access_flags;
u2 name_index; //方法的简单名称
u2 descriptor_index; //方法的描述符
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中访问标志为:
方法的简单名称:没有参数修饰的名称
方法的全限定名称:所属类的全限定名称(分隔符从’.‘换成’/’) + 简单名称
方法描述符:参数列表+返回值,参数列表按照参数的严格顺序放在一组小括号’()'中。相较于对参数类型的描述,方法描述多了一个void空返回类型,用’V’标识,如:
见最后的属性表结合一节
在Class文件、字段表、方法表中都可以携带自己的属性表集合
虚拟机规范定义的属性:
属性表结构:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
对于每个属性,它的名称都要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值 的结构则完全是自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可。
Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。
结构如下:
Code_attribute {
u2 attribute_name_index;//指向常量池的一个字符串,值固定为"Code"
u4 attribute_length;//属性值的长度,固定为属性表长度减去6个字节
u2 max_stack;//操作数栈深度最大值
u2 max_locals;//局部变量表所需的存储空间,单位是变量槽
u4 code_length;//字节码长度,字节码长度是一个u4类型的长度值,理论上可以达到2^32,但JVM实际上只使用了u2的长度
u1 code[code_length];//存储字节码指令的一系列子接口
u2 exception_table_length;//显示异常处理表长度
{
//如果当字节码从第start_pc行到end_pc行之间(不含第end_pc行)出现了类型为catch_type或者其子类的异常,则转到第handler_pc行进行处理
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
这里的Exception属性时方法表中与Code属性平级的属性,而不是Code属性中的Exception_table属性集合。
Exception属性的作用是列举出方法中可能抛出的受查异常,即throws
关键字之后列举的异常。它的结构如下:
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;//方法可能抛出的异常种类数
u2 exception_index_table[number_of_exceptions];//可能抛出异常的类型索引
}
用于描述Java源码行号与字节码行号之间的对应关系。在javac时,可以使用-g:none或-g:lines选项来取消或者开启生成这项信息。如果不生成这项信息,当抛出异常时,对应处的堆栈信息中将不会显示出错的行号;在调试时也无法按照源码行来设置断点。其结构如下:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;//字节码行号
u2 line_number; //源码行号
} line_number_table[line_number_table_length];
}
还有很多,后面再慢慢补充。