参考资料:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明
1、类文件结构
-
Class文件是一组以字节为基础单位的二进制流,各个数据项目严格按照顺序排列紧凑的在文件中,中间没有任何分隔符。
-
Class文件采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有“
无符号数
”和“表
”两种数据类型
无符号数:属于基本数据类型,以u1、u2、u4、u8分别表示1个字节,2个字节...这样。可以用来描述索引引用、数字、数量值等。
表:是由多个无符号数或者其他表作为数据项构成的有层次关系的复合数据类型,一般以
_info
结尾,整个Class本质上也可以视作一张表
伪代码描述如下:
ClassFile {
u4 magic; // 魔数
u2 minor_version; // 主版本号
u2 major_version; // 次版本号
u2 constant_pool_count; // 常量池数量
cp_info constant_pool[constant_pool_count-1]; //常量池,类名/字段名/字符串字面量
u2 access_flags; // 访问标识,public\final
u2 this_class; //当前类索引
u2 super_class; //父类索引,Object为0
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]; //属性表集合。存储类、字段、方法以及代码额外信息。
}
-
类变量:属于类本身变量,用static声明。
-
实例变量:在类中声明,实例初始化时创建。
1.1 魔数与Class文件的版本
-
紧接着魔数的4个字节存储的是Class文件的版本号。
-
第5和第6字节是次版本号
-
第7和第8个字节是主版本号
-
Java的版本号从45开始,JDK1.1之后每个JDK大版本发布 主版本号向上加1。
-
Java虚拟机会拒绝执行超过其版本号的Class文件。
1.2 常亮池
接着主、次版本号之后的就是常量池入口,常量池类似于Class文件的资源仓库。
常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值。
-
当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存中。
常量池中每个常量都是一个表,截止JDK13,常量表中分别有17中不同类型的常量。
1.3 访问标志
-
在常量池结束后,紧接的2个字节代表访问标志。
-
这个标识用于识别一些类或者接口层次的访问信息
-
包括:Public、final、abstract等
1.4 类索引、父类索引和接口索引集合
-
类索引、父索引都是一个u2类型的数据
-
接口索引集合是一组u2类型的数据集合
-
Class文件中由这三项数据来确定该类的继承关系
Java不允许多继承,所以只有一个父索引,除了Object外所有Java类都有父类。
1.5 字段表集合
字段表用于描述接口或者类中声明的变量,包括类级变量和实例级变量,但是不包括方法内部声明的局部变量。
字段表集合主要包括以下4部分:
-
访问标志:表示当前字段的访问权限和特征(Public、Private、Static)等。
-
名称索引:对常量池项的引用,代表字段的简单名称(就是指没有修饰符的方法或字段)。
-
描述符索引:对常量池项的引用。用来描述字段数据类型、方法参数列表和返回值。
-
属性表集合(Attribute):用于存储一些额外的信息例如:synthetic属性等。
结合Java代码示例:
// java类
public class Person {
public int age;
private String name;
}
// 常量池
Constant pool:
#1 = Utf8 age
#2 = Utf8 I
#3 = Utf8 name
#4 = Utf8 Ljava/lang/String;
...
// 字段表集合
Fields:
public int age;
Access flags: ACC_PUBLIC
Name index: #1 // 指向常量池中存储 "age" 的项
Descriptor index: #2 // 指向常量池中存储 "I" 的项
Attributes:
private java.lang.String name;
Access flags: ACC_PRIVATE
Name index: #3 // 指向常量池中存储 "name" 的项
Descriptor index: #4 // 指向常量池中存储 "Ljava/lang/String;" 的项
Attributes:
1.6 方法表集合
它提供了关于类或接口中声明的每个方法的详细信息,使得 JVM 能够正确地加载、链接和执行这些方法。
-
Class文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式。
-
方法表结构如同字段表一样:依次包括访问标志、名称索引、描述符索引、属性表集合几项。
如果父类方法没有在子类中重写,方法表集合中就不会出现来自父类的方法信息。
在java中重载方法需要有相同的方法名称和不同的Java特征签名。
Java方法的特征签名指一个方法中各个参数在常量池中的字段符号引用的集合(包括方法名称,参数数量,参数类型/顺序)但是不包括返回值,也因此返回值不同不能作为java重载的条件。
但是,Class文件的特征签名,还包括方法返回值以及受查检查表。
1.7 属性表集合
Class文件、字段表、方法表可以携带自己的属性表集合,用于描述特定信息。
❯ javac JvmTest/StaticDispatch.java
❯ javap -verbose JvmTest.StaticDispatch
可以查看java文件的字节码
1.7.1 Code的属性
Java程序方法体里面的方法经过Javac编译后,最终变为字节码指令存储在Code属性内。
-
Code属性出现在方法表的属性集合中。当然不是所有方法表都存在这个属性,譬如接口或者抽象类中的方法就不存在Code属性。
-
包含方法的字节码指令、局部变量表、操作数栈大小以及异常处理表。
1.7.2Exceptions
这里的Exceptions属性是在方法表中与Code属性平级的一项属性,作用是列举出方法可能抛出的售查异常,也就是方法描述时throws关键字后面的异常。(注意这里的Exceptions与Code中的异常处理表不同)
1.7.3 LineNumberTable属性
该属性描述Java源码行号与字节码行号之间的对应关系,主要用于调试。
如果没有就无法在堆栈中显示出错的行号,也没办法设置断点调试。
1.7.4 LocalVariableTable属性和LocalVariableTypeTable属性
-
LocalVariableTable属性用于描述栈帧中 局部变量表 的变量与Java源码中定义的变量之间的关系。
-
它会默认生成
-
如果没有生成这项属性,最大的影响就是当其他人引用这个方法的时候,所有的参数名称都会丢失。 譬如IDE会使用诸如arg0、arg1之类的占位符代替原有的参数名
1.7.5 SourceFile及ScourceDebugExtension属性
-
SouceFile属性用于记录生成这个Class文件的源码名称。
-
在Java中,大多数的类来说,类名和文件名是一致的。当然内部类这种特殊情况除外。
-
不生成这项属性的话,抛出异常时,堆栈中将不会显示出错代码所属的文件名。
-
为了方便在编译器和动态生成的Class中加入供程序员使用的自定义内容,在JDK5中,新增加了SourceDebugExtension属性来存储额外的代码调试信息。
1.7.6ConstantValue属性
-
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。
-
只有被static关键字修饰的变量才可以使用这项属性。
-
目前Oracle公司使用的Javac编译器,选择的是如果同时使用final和static关键字来修饰一个变量,并且这个变量的数据类型是基本数据类型后者java.lang.String的话,将会生成ConstantValue属性来进行初始化。
1.7.7InnerClasses属性
-
InnerClasses属性用来记录内部类与宿主之间的关联。
-
如果一个类包含了内部类,编译器将会它和它所包含的内部类生成InnerClasses属性
1.7.8 Deprecated及Synthetic属性
-
Deprecated属性用来标记某个类、字段或者方法已经被程序作者定义为不再推荐使用,在代码中可以通过@deprecated注解来设置。
-
Synthetic属性代表此字段或者方法不是由Java源码直接产生的,而是由编译器自己添加的。
1.7.9 Signature属性
-
指定了该类或者接口的泛型签名信息。
-
Java语言的泛型采用的是擦除法实现的伪泛型,字节码(Code属性)中所有的泛型信息编译在编译之后都通通被擦除掉。
1.7.10 BootstrapMethods属性
-
它是一个复杂的变长属性,位于类文件的属性表中。
-
这个属性用于保存invokedynamic指令引用的引导方法限定符。
1.7.11 MethodParameters属性
-
MethodParameters是用在方法表中的变成属性。用于记录方法的各个形参名称和信息。
-
为了解决程序的传播和二次复用的不便等问题,JDK8中加入了这个属性。