高亮的杂货铺

JVM从0到1-class文件属性表

2020-03-14 · 5 min read
JVM Java

属性表(attribute_info)在前面的讲解中已经出现过多次,class文件、字段表、方法表都可以由自己携带的属性表集合,以描述某些场景专有的信息。虚拟机规范对属性表的限制稍微宽松一些,不要求各个属性具有严格顺序,而且只要不与已有属性崇明,任何人实现的编译其都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时,会忽略它不认识的属性。

对于每一个属性,他的名称都要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构是完全自定义的,只需要通过一个u4长度属性来表明属性值所占用的位数即可。

截止到Java虚拟机规范的Java SE 12版本,预定义的属性项已经增加到了29项。详细见后面的表格

Code属性

Java程序的方法体经过Javac编译器处理后,最终转换为字节码存储在Code属性中,Code属性出现在方法表中的属性集合之中,但并非所有的方法表都必须有这个属性,比如接口和抽象类中的方法,就不存在Code属性,如果方法表有Code属性存在,那么他的结构如下

其中,第一个u2 attriibute_name_index,指向了常量池中的一个utf8类型常量的索引,此常量固定值为Code,attribute_length表示了属性值的长度。

max_stack代表了操作数栈深度的最大值,在方法执行的任意时刻,操作数栈都不会超过这个深度,虚拟机在运行时根据这个值来分配栈帧中的操作树栈深度。

max_locals代表了局部变量表所需的存储空间,在这里,max_locals的单位是槽(slot),变量槽是虚拟机为局部变量分配内存的最小单位,对于byte、char、float、int、short、boolean和returnAddress等长度不超过32位的数据类型,每个局部变量占用一个变量槽,而double和long这种64位的数据类型则需要两个变量槽。 方法参数(包括实例方法中隐含的this)、显示异常处理程序的参数(在catch中定义的异常值)、方法体中定义的局部变量都依赖局部变量表来存放。 但是要注意,并不是方法中有多少个局部变量,就把这些局部变量表所占空间之和作为max_locals的值,操作数栈和局部变量表决定了一个方法的栈帧大小,不必要的数量会导致内存的浪费。 Java虚拟机的做法是将局部变量表的局部变量池进行重用,当代码执行超过一个局部变量的作用域时,这个局部变量所占用的局部变量槽可以被其他局部变量使用,javac编译器会根据变量的作用域来分配变量槽来给各个变量使用,然后根据同时生存的最大局部变量数和类型来计算出max_locals的大小。

code_length和code用来存储Java源程序编译后产生的字节码指令,code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。虽然code_length是一个u4类型的长度值,也就是理论上可以达到2的32次幂,但是根据java虚拟机规范,一个方法不允许超过65535条字节码指令,即实际值使用到了u2的长度。

在字节码指令之后的是显式异常处理表,异常表对于Code属性来说并不是必须存在的。其结构如图

字段的含义为,当字节码从start_pc行到end_pc行之间出现了类型为catch_type或者其子类的异常时,则转到第handler_pc行继续处理。 当catch_type的值为0时,代表任意异常情况都需要转到handler_pc行进行处理。

Exceptions属性

这个Exceptions属性是方法表中与Code属性同级的一项属性。作用是列举一个方法可能抛出的受检查异常(Checked Exceptions),也就是方法描述时,throws关键字后面列举的异常,其结构如下图

其中exception_index_table指向了常量池中CONSTANT_Class_info的索引。

LineNumberTable属性

LineNumberTable属性是用于描述Java源代码行号和字节码行号之间关系的。他不是运行时必须的,可以通过javac的参数 -g参数进行取消或生成。 如果取消这个属性,最大的影响就是在运行时抛出的异常不会包含报错行号,在调试程序时也无法根据源码行号来设置断点。

LocalVariableTable和LocalVariableTypeTable属性

LocalVariableTable属性是用来描述栈帧中局部变量表的变量与java源码中定义的变量之间的关系,也不是运行时必须的属性,如果不生成,最大的影响是对IDE工具调试时无法根据参数名从上下文中获取参数值。

在JDK5引入泛型后,LocalVariableTable属性增加了一个姐妹属性,LocalVariableTypeTable,其结构与LocalVariableTable很相似,仅仅是把描述字段的字段描述符替换成了字段的特征签名。由于描述符中泛型参数化类型被擦除,描述符不能准确描述泛型类型了,因此出现了LocalVariableTypeTable属性,使用特征签名来完成泛型的描述。

附录 预定义属性列表