属性表在前面的讲解中出现多次,在Class文件、字段表、方法表都可以携带自己的属性表集合,用于描叙某些场景专有的信息。为了正确解析Class文件,《Java虚拟机规范(第二版)》中预定义了9项虚拟机实现应当识别的属性。然而在最新的《Java虚拟机规范(Java SE7)》中属性表已经增加到了21项。当然我们不用全部记住它们,只需要熟悉其中的几个关键属性。
1.属性表的基本结构
本次博客只谈其中的5种属性
每个属性的名称都引用自常量池中一个CONSTANT_Utf8_info类型的常量来表示,属性值的结构则完全是自定义,只需要一个u4长度属性去说明属性值所占用的位数即可。
基本属性表的结构图如下所示:
1.1 Code属性
Java方法体中的代码经过Javac编译之后,最终变为字节码存储在Code属性中,Code属性出现在方法表的属性集合之中。接口或抽象类中的方法并不存在Code属性,因为他们并没有对方法进行具体的实现。
Code属性表的结构如下图:
在这里我尝试挑几个概念上比较重要来进行记录。
-
max-stack,要解释清楚它,需要了解操作数栈,栈帧等知识,所以这个属性字段先不谈。
-
max_locals,这个挺有意思。它代表了局部变量所需的存储空间。单位是Slot,虚拟机为局部变量分配内存使用的最小单位。对于byte、char… …这种长度不超过4个字节的数据类型,每个局部变量占用一个Slot,而double、long这两种64位的数据类型则需要两个Slot来存放。方法参数(this)、显式异常处理器参数(try-catch)、方法体中的局部变量都需要使用局部变量表来存放。计算max_locals的值也不是方法中用到了多少个局部变量,就将每个局部变量所占用的Slot算出来最后进行简单求和。事实上局部变量表中的Slot可以重用,当代码执行超出一个局部变量的作用域时,这个局部变量所占用的Slot可以被其他局部变量所使用。Javac编译器会根据变量的作用域来分配Slot给每个变量使用,然后计算出max_locals的大小。
-
code_length不用说代表的是字节码长度。它是一个u4类型的长度值,理论上最大值可以达到2的32次方-1,但Java虚拟机规范限制一个方法不允许超过65535条字节码指令,所以它实际只使用了u2的长度,一旦超过这个长度,编译器会拒绝编译。
Code属性是Class文件中最重要的一个属性,Java程序可以分为代码和元数据,也就是方法体中的代码与字段、类、方法定义等其他信息。因此在Class文件中,只有Code属性用于描述Java方法,其他都用于描述元数据。
我已将常量池部分省略:
{
public com.basic.java.classStructure.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/basic/java/classStructure/TestClass;
public int c();
descriptor: ()I
flags: ACC_PUBLIC
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 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/basic/java/classStructure/TestClass;
}
SourceFile: "TestClass.java"
因为原Java文件中实际上有两个方法,一个实例构造器,一个inc方法。因此第一个public TestClass();代表的是实例构造器。我们可以看到args_size=1,说明这个方法有一个参数,虽然源代码里面两个方法都没有显式参数,但是我们都知道这个参数肯定是this。
当然,这个this只对实例对象有效,也就是说,如果inc方法被声明为static,那么它的args_size就会等于0。
往下看是三条字节码指令,关于字节码指令的内容目前也不进行讨论,所以暂且跳过。
异常表
按道理来说,字节码指令之下就是异常表(显式异常处理表),但是此代码并没有生成异常表。所以这个部分对Code来说并不是必须的。
来看一下异常表的结构:
异常表包含4个字段,这些字段的含义为:如果字节码从第start_pc到end_pc行之间(不包含第end_pc)行出现了类型为catch_type或其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的索引),则转到第handler_pc行继续处理。当catch_type的值为0时,代表任何的异常情况都需要转向到handler_pc行进行处理。
异常表实际上是Java代码的一部分,编译器使用异常表而不是简单的跳转命令来实现Java异常及finally处理机制。注:字节码的“行”是一种形象的描述,指的是字节码相对于方法体开始的偏移量,而不是Java源代码的行号。
1.2 Exception属性
属性表结构如下:
这里的Exceptions属性是在方法表中与Code属性平级的一项属性,而不是Code属性表中的异常属性表。Exceptions属性表的作用是列举出方法中可能抛出的受查异常(Checked Exception),也就是在方法描述时在throws关键字后面列举的异常。
此属性表中的number_of_exceptions项表示方法可能抛出number_of_exceptions种受检查异常,每一种受检查异常使用一个exception_index_table项表示,指向常量池中CONSTANT_Class_info型常量表的索引,代表了该受检查异常的类型。