Class文件结构介绍[属性表集合]
属性表 在前面的内容中属性表(attribute_info)已经出现多多次了,在Class文件、字段表、方法表中都可以携带自己的属性集合,用于描述某些场景专有的信息 与class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松些,不在要求各个属性表具有严格顺序,并且只要不与已有属性名称重复,任何人实现的编译器都可以向属性表中写入自己的属性信息,java虚拟机会...
属性表
在前面的内容中属性表(attribute_info)已经出现多多次了,在Class文件、字段表、方法表中都可以携带自己的属性集合,用于描述某些场景专有的信息
与class文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松些,不在要求各个属性表具有严格顺序,并且只要不与已有属性名称重复,任何人实现的编译器都可以向属性表中写入自己的属性信息,java虚拟机会忽略掉它不认识的属性,在最新的《Java虚拟机规范(JavaSE7)》版本中,预定义属性有21项,
属性表的总体结构:
名称 | 类型 | 属性 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
attribute_info又可细分为以下21种(即《Java虚拟机规范(Java SE 7)》中预定义了的21项虚拟机实现应当能识别的属性):
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表中 | Java代码编译成的字节码指令(即:具体的方法逻辑字节码指令) |
ConstantValue | 字段表中 | final关键字定义的常量值 |
Deprecated | 类中、方法表中、字段表中 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表中 | 方法声明的异常 |
LocalVariableTable | Code属性中 | 方法的局部变量描述 |
LocalVariableTypeTable | 类中 | JDK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 |
InnerClasses | 类中 | 内部类列表 |
EnclosingMethod | 类中 | 仅当一个类为局部类或者匿名类时,才能拥有这个属性,这个属性用于表示这个类所在的外围方法 |
LineNumberTable | Code属性中 | Java源码的行号与字节码指令的对应关系 |
StackMapTable | Code属性中 | JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配 |
Signature | 类中、方法表中、字段表中 | JDK1.5新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息 |
SourceFile | 类中 | 记录源文件名称 |
SourceDebugExtension | 类中 | JDK1.6中新增的属性,SourceDebugExtension用于存储额外的调试信息。如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码运行在Java虚拟机汇中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension就可以存储这些调试信息。 |
Synthetic | 类中、方法表中、字段表中 | 标识方法或字段为编译器自动产生的 |
RuntimeVisibleAnnotations | 类中、方法表中、字段表中 | JDK1.5中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations属性,用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的。 |
RuntimeInvisibleAnnotations | 类中、方法表中、字段表中 | JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations相反用于指明哪些注解是运行时不可见的。 |
RuntimeVisible ParameterAnnotations | 方法表中 | JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations类似,只不过作用对象为方法的参数。 |
RuntimeInvisible ParameterAnnotations | 方法表中 | JDK1.5中新增的属性,作用与RuntimeInvisibleAnnotations类似,只不过作用对象为方法的参数。 |
AnnotationDefault | 方法表中 | JDK1.5中新增的属性,用于记录注解类元素的默认值 |
BootstrapMethods | 类中 | JDK1.7新增的属性,用于保存invokedynamic指令引用的引导方法限定符 |
Code属性
java程序方法体重的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内,Code属性出现在方法表的属性集合中(如下图),但并非所有的方法都必须存在这个属性,譬如接口或者抽象类中的方法就不存在Code属性,如果方法表有Code属性存在。
Code属性的结构图:
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
max_stack | u2 | 1 |
max_locals | u2 | 1 |
code_length | u4 | 1 |
code | u1 | code_length |
exception_table_length | u2 | 1 |
exception_table | exception_info | exception_table_length |
attribute_count | u2 | 1 |
attributes | attribute_info | attribute_count |
attribute_name_index
指向CONSTANT_Utf8_info型常量的索引,常量值固定为“Code”,它代表了属性的名称。
attribute_length:指示了属性值的长度。由于attribute_name_index与attribute_length一共占6个字节,所以属性值的长度又等于属性表总长度-6。
max_stack:操作数栈(Operand Stacks)允许深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个深度. 虚拟机的时候需要根据这个值来分配栈帧(Stack Frame)中的操作占深度。
max_locals:代表了局部变量表所需的存储空间。在这里,max_locals的单位是Slot槽。Slot是虚拟机为局部变量分配内存所使用的最小单位。一个Slot的空间大小为四字节,对于byte、char、float、int、 short、 boolean、和returnAddress等长度不超过32位的数据类型,每个局部变量占用一个Slot;而double和long这种64位的数据则需要两个连续的Slot来存储。
注:并不是在方法中用到了多少局部变量,就把这些局部变量所占用放入Slot个数之和作为,max_locals的值,原因是局部变量表中的Slot槽可以重用。当代码执行超出一个局部变量的做用户与使时,这个局部变量所占用放入Slot可以被其它局部变量所使用,Javac编译器会根据变量的作用域来分配Slot给各个变量使用,然后计算出max_locals的大小。
code_length、code:用来存储Java源码编译后的字节码指令。code_length代表字节码长度;code是用于存储Java字节码指令的一系列字节流。
public void fun1(){
int b = 20;
int c = 30;
int d = b+c+age;
System.out.println(d);
}
对照虚拟机字节码指令表 我们来看下Code中的指令
指令 | 字节码 | 说明 |
---|---|---|
10 14 | bipush 20 | 将20推送至栈顶 |
3C | istore_1 | 将栈顶20存入第二个本地变量(b) |
10 1E | bipush 30 | 将30推送至栈顶 |
3D | istore_2 | 将栈顶30型数值存入第三个本地变量( c ) |
1B | iload_1 | 将第二个int型本地变量推送至栈顶(b) |
1C | iload_2 | 将第三个int型本地变量推送至栈顶( c ) |
60 | iadd | 将栈顶两int型数值相加并将结果压入栈顶(b+c=50) |
2A | aload_0 | 将第一个引用类型本地变量推送至栈顶(age) |
B4 00 13 | getfield #19 | 获取指定类的实例域, 并将其压入栈顶age |
60 | iadd | 将栈顶两int型数值相加并将结果压入栈顶(age+50=68) |
3E | istore_3 | 将第四个int型本地变量推送至栈顶(68) |
B2 00 1A | getstatic #26 | 获取指定类的静态域, 并将其压入栈顶 java/lang/System.out |
1D | iload_3 | 将第四个int型本地变量推送至栈顶(68) |
B6 00 20 | invokevirtual #32 | 调用实例方法(调用println方法) |
B1 | return | 从当前方法返回void |
javap输出:
Exceptions属性
Exception属性的作用是列举出方法的声明异常。
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
number_of_exceptions | u2 | 1 |
exception_index_table | u2 | number_of_exceptions |
exception_index_table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了异常的类型。
LineNumberTable属性
LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。
注:LineNumberTable并不是必须的,javac编译时,可通过-g:none或-g:lines来取消或生成这项信息。
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
line_number_table_length | u2 | 1 |
line_number_table | line_number_info | line_number_table_length |
LocalVariableTable属性
LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间关系。
注:LocalVariableTable属性不是必须的,在javac编译时,可通过-g:none或-h:vars来取消或关闭这项信息。如果没有生成这项信息,最大的影响就是当别人引用这个方法时,所有的参数名称都将失去,IDE将会使用诸如arg0、arg1之类的占位符来代替原有的参数名,这对程序没什么影响,但是会对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获取参数值。
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
local_variable_table_length | u2 | 1 |
local_variable_table | local_variable_info | local_variable_table_lengt |
local_variable_info的结构
名称 | 类型 | 数量 |
---|---|---|
start_pc | u2 | 1 |
length | u2 | 1 |
name_indec | u2 | 1 |
descriptor_index | u2 | 1 |
index | u2 | 1 |
start_pc和length属性分别代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围的覆盖长度。即:确定了这个局部变量的作用范围。
nam_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info型常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符。
index是这个局部变量在栈帧局部变量表中Slot的位置。如果这个局部变量是64位的,那么它占用的两个连续的Slot的位置是index和index+1。
JDK1.5之后,新增了一个LocalVariableTable属性的“姐妹属性”—LocalVariablrTypeTablem,这个新增的属 性结构与LocalVariableTable非常相似,仅仅是把记录字段描述符的descripor_index替换成了字段的特征签名(Signature),对于非泛型类型来说,描述符和特征签名描述的信息基本是一致的,但是引入泛型后,由于描述符中泛型的参数类型被擦除掉,描述符就不能准确地描述泛型类型了,因此出现录入LocalVariableTypeTable。
SourceFile属性
SourceFile属性用于记录生成这个Class文件的源码文件名称
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
sourcefile_index | u2 | 1 |
sourcefile_index是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件的文件名
ConstantValue属性
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(即:类变量)才可以使用这项属性。对于类中的实例变量(即:非静态变量),赋值操作是在实例构造器中进行的。对于类变量(即:静态变量),有两种赋值方式可以选择:一种是在类构造器方法中进行赋值;另一种是使用ConstantValue属性进行赋值。
注:目前Sun Javac编译器的选择是:如果这个变量的数据类型分是基本类型或者java.lang.String的话,就生成 ConstantValue属性来进行初始化,如果这个变量没有被final修饰,或者并非基本数据类型及字符串,则将会选择在方法(即:类构造器)中进行初始化。
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
constantvalue_index | u2 | 1 |
InnerClass属性
InnerClass属性用于记录内部类与宿主类之间的关联
名称 | 类型 | 数量 |
---|---|---|
attribute_name_index | u2 | 1 |
attribute_length | u4 | 1 |
number_of_class | u2 | 1 |
inner_classes | inner_classes_info | number_of_class |
number_of_classes代表记录了多少个内部类信息,每一个内部类信息都由一个inner_classes_info表进行描述。inner_classes_info结构为:
名称 | 类型 | 数量 |
---|---|---|
inner_class_info_index | u2 | 1 |
outer_class_info_index | u2 | 1 |
inner_name_index | u2 | 1 |
inner_class_access_flags | u2 | 1 |
参考《深入理解Java虚拟机》
更多推荐
所有评论(0)