jvm探秘五:Class类文件结构之属性表

来源:互联网 发布:大连东华软件股份公司 编辑:程序博客网 时间:2024/06/15 05:54

概述


在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属性结束。

原创粉丝点击