jvm探秘五:Class类文件结构之属性表
概述在Class文件、字段表和方法表都可以携带自己的属性信息,这个信息用属性表进行描述,用于描述某些场景专有的信息。与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范
概述
在Class文件、字段表和方法表都可以携带自己的属性信息,这个信息用属性表进行描述,用于描述某些场景专有的信息。
与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性(预定义属性已经增加到21项)
- 常见的属性
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类文件、字段表、方法表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTale | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable Code属性 | 方法的局部变量描述 | |
SourceFile | 类文件 | 源文件名称 |
Synthetic | 类文件、方法表、字段表 | 标识方法或字段是由编译器自动生成的 |
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_utf8_info类型的常量类表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性区说明属性值所占用的位数即可.
- 属性表定义的结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u2 | attribute_length | 1 |
u1 | info | attribute_length |
一:Code属性
java程序方法体中的代码经过javac编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合中,抽象类和接口不存在code属性。
code属性是class类文件中最重要的属性。class文件可以分为代码(code,方法体里面的Java代码)和元数据(Metadata,包括类,字段,方法定义及其他信息)两部分,code属性描述代码,其他数据项都用于描述元数据。
- code属性表结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_locals | 1 |
u4 | code_length | 1 |
u1 | code | code_length |
u2 | exception_table_length | 1 |
exception_info | exception_table | exception_length |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
- Class类文件中对应的code属性代码
- attribute_name_index
指向CONSTANT_Utf8_info型常量的索引,常量值规定为”Code“,代表了该属性的属性名称。测试用例中值为0x0009,为常量池中的第九项。
#9 = Utf8 Code
- attribute_length
属性值的长度,用8个字节表示,测试用例中值为:0x00000006表示Code属性值的字节长度为47。
- max_stack
所需操作数栈的深度的最大值,方法执行的任何时刻,操作数栈都不会超过这个深度。虚拟机运行时需要根据这个值分配栈帧。
测试用例中为:0x0001,说明需要的最大深度为1,没有递归。
- max_locals
代表局部变量表所需的存储空间,单位为Slot(插槽),Slot是虚拟机为局部变量分配内存空间使用的最小单位。长度不超过32位的数据类型占用1个Slot(byte,char,float,int,short,boolean,returnAddress),64位的数据类型(long和double)占用2个Slot。Slot可以重用。当代码执行超过局部变量的作用域时,Solt可以被其他局部变量使用。
测试用例中为:0x0001,说明需要的局部变量槽位为1 - code_length和code
这两个用来存储java源程序编译后生成的字节码指令。code_lenth代表字节码长度(u4),code用于存储字节码指令的一系列字节流。每个字节指令是一个u1数据类型(1bit).虚拟机读取到一个一个字节码时,就可以找出对应的这个字节码是什么指令。java虚拟机最多可以有256条指令,现在有200多条指令。
字节码区域长度为0x00000005,虚拟机读取到后会顺序依次的读取之后的5个字节,并根据字节码指令表翻译为对应的字节码指令。
过程为
1:读入2A,对应指令为aload_0,将低0个Slot槽位为reference类型的本地变量推送到栈的顶端。
2:读入B7,对应指令为invokespecial,将栈顶的reference类型的数据所指向的对象作为方法的接收者,调用此对象的实例构造方法。这条指令带有一个u2类型的参数,为具体调用的哪一个方法,指向常量池中一个
CONSTANT_Methodref_info类型常量,即此方法的符号应用。
3:读入00 0A,0x000A对应为常量池第10个常量,为实例构造器init方法的符号引用
#10 = Methodref #3.#11 // java/lang/Object."<init>":()V
读入B1,对应指令为return,含义为返回此方法,返回值为void。这条指令执行完后当前方法结束。
属性表对应的方法表完整代码
{
public cn.yuli.jvm.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/yuli/jvm/TestClass;
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #18 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcn/yuli/jvm/TestClass;
}
这两个为实例构造器方法init()和inc(),locals和Args_size=1是应为有this引用,局部变量第一槽位存储的是本对象的this引用,如果方法定义为static,locals就为0了。
- exception_table_length和exception_info
exception_table_length是u2类型,4个字节。异常表对Code属性来说不是必须存在, 在本实例中为0x0000,说明不存在异常表。
- attributes_count和attributes
为Code表中包含的其他属性,如:
LineNumberTable属性,用于描述java源码行号和字节码行号之间的对应关系。
LocalVariableTable属性,用户描述栈中局部变量表中的变量和java源码中定义的变量的关系。
继续读字节码文件,0x0000之后是0x0002,说明Code表带有两个属性。第一个属性是0x000C,转换为10进制为12查常量表为
#12 = Utf8 LineNumberTable
说明是LineNumberTable属性,根据LineNumberTable属性结构,接下来为u4类型的长度描,0x0000006,为6位bit。
接下来2位为0x0001,即该line_number_table中只有一个line_number_info表,字节码行号start_pc为0x0000,l源码行号ine_number为0x0006,LineNumberTable属性结束。
更多推荐
所有评论(0)