接着上一篇,我们继续补充
Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式,方法表的结构如同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项,如下表所示。这些数据项目的含义非常类似,仅在访问标志和属性表集合的可选项中有所区别。
因为volatile
和transient
不能修饰方法,所以方法表的访问标志中没有了ACC_VOLATILE标志和ACC_TRANSIENT标志。与之相对的,synchronized、native、strictfp和abstract关键字可以修饰方法,所以方法表的访问标志中增加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT标志。对于
方法表,所有标志位及其取值可参见下表
方法的定义可以通过访问标志、名称索引、描述索引来表达,方法中的代码可以通过Code
属性来展示,具体可见属性表内容
与字段表集合相对应的,如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现由编译器自动添加的方法,最典型的便是类构造器
方法和实例构造器
方法。
属性表(attribute_info)
,在Class文件、字段表、方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
任何人实现的编译器都可以向属性表中写人自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。为了能正确地解析Class文件,《Java虚拟机规范(第2版)》中预定义了9项虚拟机实现应当能识别的属性,具体如下表所示。
Code属性
Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口或抽象类中的方法就不存在Code属性,如果方法表有Code属性存在,那么它的结构将如下表所示。
attribute_name_index
是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为“Code”,它代表了该属性的属性名称,attribute_length
指示了属性值的长度,由于属性名称索引与属性长度一共是6个字节,所以属性值的长度固定为整个属性表的长度减去6个字节。
max_stack
代表了操作数栈(Operand Stacks)深度的最大值
max_locals
代表了局部变量表所需的存储空间
code_length
和code
用来存储Java源程序编译后生成的字节码指令,code_length
代表字节码长度,code
用于存储字节码指令的一系列字节流
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 12: 0
Exceptions属性
Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Excepitons),也就是方法描述时在throws关键字后面列举的异常。它的结构如下表所示
此属性中的number_of_exceptions
项表示方法可能抛出number_of_exceptions种受查异常,每一种受查异常使用一个exception_index_table
项表示,exception_index_.table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型
LineNumberTable属性
LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。它并不是运行时必需的属性,但默认会生成到Class文件中,可以使用javac -g:none or -g:lines
选项来取消或要求生成这项信息
LocalVariableTable属性
LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,它不是运行时必需的属性,默认也不会生成到Class文件之中。可以使用Javac的-g:none或-g:source选项来关闭或要求生成这项信息。与LocalVariableTypeTable是一致的
SourceFile属性
SourceFile属性用于记录生成这个Class文件的源码文件名称。这个属性也是可选的,可以使用Javac的-g:none或-g:source选项来关闭或要求生成这项信息。
ConstantValue属性
Constant Value属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才可以使用这项属性。在Java程序里面类似“intx=l23”和“static int x=123”这样的变量定义是非常常见的事情,但虚拟机对这两种变量赋值的方式和时刻都有所不同。对于非static类型的变量(也就是实例变量)的赋值是在实例构造器
方法中进行的;而对于类变量,则有两种方式可以选择:赋值在类构造器
方法中进行,或者使用Constant Value属性来赋值。
如果同时使用final和static来修饰一个变量(或者说常量更贴切),并且这个变量的数据类型是基本类型或java.lang.String的话,就生成Constant Value属性来进行初始化,如果这个变量没有被final修饰,或者并非基本类型及字符串,则选择在
方法中进行初始化。
InnerClasses属性
InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它及它所包含的内部类生成InnerClasses属性。
Deprecated及Synthetic属性
Deprecated和Synthetic两个属性都属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概念。
Deprecated属性用于表示某个类、字段或方法,已经被程序作者定为不再推荐使用,它可以通过在代码中使用@deprecated
注释进行设置。
Synthetic属性代表此字段或方法并不是由Java源码直接产生的,而是由编译器自行添加的,在JDK1.5之后,标识一个类、字段或方法是编译器自动产生的
Class文件结构自Java虚拟机规范第一版订立以来,已经有十多年的历史。这十多年间,Java技术体系有了翻天覆地的改变,相对于语言、API及Java技术体系中其他方面的变化,Class文件结构一直处于一个相对比较稳定的状态,Class文件的主体结构几乎没有发生过变化,对Class文件格式的改进都集中在向访问标志、属性表这些在设计上本就可扩展的数据结构中添加内容。
而属性表集合中,在JDK1.5和JDK1.6版本内一共增加了10项新的属性。这些属性的名称及具体作用可以参见下表