上篇必看:实例分析Class字节码文件(二)
六、属性表集合
在前面介绍的,字段表集合和方法表集合中都有使用到属性表,结构均为:
首先用2个字段标识属性表集合的大小,之后就是属性表的具体内容,属性表结构如下:
attribute_name_index
: 指向运行时常量池中的某个常量
attribute_length
: 属性长度
info
:属性具体内容
下面开始介绍,java虚拟机可识别的属性
1.Code属性
Java程序方法体中代码经过编译后,最终变成字节码指令存储在该属性中,Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口或抽象类中的方法就不存在Code属性
Code
属性表结构如下:
① attribute_name_index
: 属性名称,指向常量,固定值为Code
② attribute_length
: 属性值的长度
③ max_stack
: 操作数栈(Operand Stacks)深度的最大值,在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度
④ max_locals
: 局部变量表所需的空间,单位是Slot,Slot是虚拟机为局部变量表分配内存所使用的最小单位。对于byte
、char
、float
、int
、short
、boolean
、returnAddress
等长度不超过32位的数据结构,占用一个Slot,而double
和long
这种64位的数据结构需要两个Slot。
注:局部变量表存放的数据包括:
1、方法参数(包括实例方法中隐藏的参数this)
2、try-catch块,catch定义的异常
3、方法体中的局部变量另外,并不是方法中用到了多少个局部变量,就把这些局部变量所占Slot之和作为
max_locals
的值,原因是局部变量表中的Slot可以复用。例如if
语句
⑤code_length、code
:是用来存储Java源程序编译后生成的字节码指令。前者代表字节码长度,后者是用于存储字节码指令的一系列字节流。每个指令用一个u1类型的单字节表示,当虚拟机读取到code中的一个字节码时,就可以对应找到这个字节码代表是是什么指令,并可以知道这个指令后面是否需要跟随参数,以及参数应当如何理解。
u1表示8bit,0~255,一共可以表示256个指令。
注:目前,jvm已经定义了其中约200条编码值对应的指令含义
code_length
是一个u4类型,理论上指令数量可以为2的32次方减1,但是在虚拟机规范中明确限制了一个方法不允许超过65535个指令,实际上只使用了u2的长度。
继续分析字节码文件,方法的code属性为例:
max_stack
: 00 01
max_locals
: 00 01
code_length
: 00 00 00 05
code
: 2a b7 00 0a b1
通过javap 查看如下:
通过上图发现:args_size = 1
,即方法参数有一个,即this
,局部变量表第一个Slot就是来存放指向当前对象实例的局部变量(this)
在code后为异常信息,该实例中无异常信息。重新举例如下:
public class TestClass {
public int inc(){
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally{
x = 3;
}
}
}
对应获取字节码信息如下:
2.Exception属性
用于列举出方法中可能抛出的受查异常,也就是方法描述时,在throws关键字后面列举的异常。结构如下:
attribute_name_index
: 属性名称,指向常量
attribute_length
: 长度
number_of_exceptions
: 抛出的异常数
exception_index_table
: 异常的长度,指向常量
3.LineNumberTable属性
用于描述Java源码行号与字节码行号之间的对应关系。可设置在Class文件中是否生成该属性,如果不生成,当抛出异常时,堆栈中将不会显示出错的行号。结构如下:
attribute_name_index
: 属性名,对应常量
attribute_length
: 长度
line_number_table_length
: 对应关系的数量
line_number_table
: 具体对应关系
4.LocalVaridbleTable属性
用于描述描述栈帧中局部变量与Java源码中定义的变量之间的关系。可设置在Class文件中是否生成该属性,如果不生成,方法的参数名称会丢失。IDE将会使用诸如arg0、arg1之类的占位符代替原有的参数名。结构如下:
local_variable_table
结构如下:
start_pc 、length
: 该局部变量起作用开始代码的位置,作用覆盖的长度,两者结合起来就是这个局部变量的作用域范围。
name_index、descriptor_index
: 局部变量名称,局部变量的描述符
index
: 该局部变量在局部变量表中Slot的位置。当这个变量类型是64位时(double和long),它占用Slot的index和index+1两个
5.SourceFile属性
用于记录生成这个Class文件的源码文件名称。可设置在Class文件中是否生成该属性,如果不生成,当抛出异常时,堆栈中将不会显示出错误代码所属的文件名(一般情况下,类名文件名是一致的),结构如下:
attribute_name_index
: 属性名称
attribute_length
: 长度
sourcefile_index
: 源文件名
6.ConstantValue属性
通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才能使用这个属性。(static不能修饰局部变量)
注:
① 非static修饰变量的赋值在实例构造器<init>
方法中进行
②static修饰变量(类变量)的赋值有两种方式选择:类构造器<clinit>
方法中或者使用ConstantValue
属性
结构如下:
attribute_name_index
: 属性名称
attribute_length
: 需要初始化的变量个数
constantvalue_index
: 需要初始化变量名称
7.InnerClasses属性
用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会生成该属性。结构如下:
attribute_name_index
: 属性名称
attribute_length
:长度
number_of_classes
: 内部类个数
inner_classes
: 用于描述关系
inner_classes_info
结构如下:
inner_class_info_index
: 内部类符号引用
outer_class_info_index
: 外部类符号引用
inner_name_index
: 内部类名称
inner_class_access_flags
: 内部类访问标志