属性表
预备知识
- javac -g Xxx.java 在生成class文件的时候生成所有调试信息
- javap -v Xxx.class 输出附加信息
属性表结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名称索引,指向一个CONSTANT_Utf8_info型常量的索引 |
u4 | attribute_length | 1 | 该属性表的长度 |
u1 | info | attribute_length | 属性值 |
Code属性表
Code属性表结构释义
-
attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,对应的值固定为‘Code’,代表了该属性的属性名称。
-
attribute_length代表了属性值的长度。属性值的长度=属性表总长度-6个字节。
-
max_stack代表操作数栈深度的最大值。虚拟机运行时根据该值来分配栈帧中操作数栈的深度。
-
max_locals 代表了局部变量表所需的存储空间。其计算单位为‘变量槽’。变量槽是JVM为局部变量表分配内存所使用的最小单位。对于byte、char、float、int、short、boolean及returnAddress等长度不超过32位的数据类型,每个局部变量占用1个变量槽,double及long这两种64位的数据类型,占用两个变量槽。
- 局部变量表中的变量槽会被复用,所以变量槽的个数不一定等于参数个数。当代码执行 超过一个变量的作用域时,其原型占用的变量槽将会被复用。
- 操作数栈及局部变量表直接决定了该方法的栈帧所耗费的内存。
-
code_length代表Java源程序编译后生成的字节码指令个数,由于一个字节码占用1字节,所以也就是字节码的长度。
- 理论值为2^32,实际最大值为65535。
-
code 用于存储编译生产的一些列字节码指令。
-
exception_info 该方法的显示异常处理表集合,非必须。
-
显示异常处理表结构
类型 名称 数量 u2 start_pc 1 u2 end_pc 1 u2 handler_pc 1 u2 catch_type 1 上表表示当字节码从[start_pc,end_pc)行出现了类型为catch_type或其子类的异常,那么就转至handler_pc行进行处理。当catch_type为0时,代表任意异常都需要跳转到handler_pc行处理。
-注意 : 此处的行指的是字节码相对于方法体开始的偏移量。
-
Code属性表实例
public class Test{private static final int size = 100;public void sout(){System.out.println(size);}public int toDouble(int prame){return 2*prame;}public int add(int p1,int p2){return p1+p2;}public static int toTriple(int prame){return 3*prame;}
}
将代码编译 javac -g Test.java
,在反编译javap -v Test.class
Classfile /D:/notes/JVM/code/Test.classLast modified 2021-3-29; size 746 bytesMD5 checksum a1a23cf998350d444d9438782f3af659Compiled from "Test.java"
public class Testminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref #5.#28 // java/lang/Object."<init>":()V#2 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream;#3 = Class #31 // Test#4 = Methodref #32.#33 // java/io/PrintStream.println:(I)V#5 = Class #34 // java/lang/Object#6 = Utf8 size#7 = Utf8 I#8 = Utf8 ConstantValue#9 = Integer 100#10 = Utf8 <init>#11 = Utf8 ()V#12 = Utf8 Code#13 = Utf8 LineNumberTable#14 = Utf8 LocalVariableTable#15 = Utf8 this#16 = Utf8 LTest;#17 = Utf8 sout#18 = Utf8 toDouble#19 = Utf8 (I)I#20 = Utf8 prame#21 = Utf8 add#22 = Utf8 (II)I#23 = Utf8 p1#24 = Utf8 p2#25 = Utf8 toTriple#26 = Utf8 SourceFile#27 = Utf8 Test.java#28 = NameAndType #10:#11 // "<init>":()V#29 = Class #35 // java/lang/System#30 = NameAndType #36:#37 // out:Ljava/io/PrintStream;#31 = Utf8 Test#32 = Class #38 // java/io/PrintStream#33 = NameAndType #39:#40 // println:(I)V#34 = Utf8 java/lang/Object#35 = Utf8 java/lang/System#36 = Utf8 out#37 = Utf8 Ljava/io/PrintStream;#38 = Utf8 java/io/PrintStream#39 = Utf8 println#40 = Utf8 (I)V
{public Test();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this LTest; // 疑问:为什么这里的Length为5?public void sout();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: bipush 1005: invokevirtual #4 // Method java/io/PrintStream.println:(I)V8: returnLineNumberTable:line 5: 0line 6: 8LocalVariableTable:Start Length Slot Name Signature0 9 0 this LTest;public int toDouble(int);descriptor: (I)Iflags: ACC_PUBLICCode:stack=2, locals=2, args_size=20: iconst_21: iload_12: imul3: ireturnLineNumberTable:line 9: 0LocalVariableTable:Start Length Slot Name Signature0 4 0 this LTest;0 4 1 prame Ipublic int add(int, int);descriptor: (II)Iflags: ACC_PUBLICCode:stack=2, locals=3, args_size=30: iload_11: iload_22: iadd3: ireturnLineNumberTable:line 13: 0LocalVariableTable:Start Length Slot Name Signature0 4 0 this LTest;0 4 1 p1 I0 4 2 p2 Ipublic static int toTriple(int);descriptor: (I)Iflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: iconst_31: iload_02: imul3: ireturnLineNumberTable:line 17: 0LocalVariableTable:Start Length Slot Name Signature0 4 0 prame I
}
现在对toDouble
方法进行分析
Code属性下
stack=2
表示操作数栈的最大深度为2locals=2
表示本地变量表的存储空间为2个变量槽args_size=2
表示方法的传参有2个- 这里有个问题,明明方法签名为
toDouble(int prame)
,只有一个参数,为什么这里显示有两个参数呢?因为在编译的时候,会自动的传入一个参数this。并放到局部变量表的0号槽中。
- 这里有个问题,明明方法签名为
- LocalVariableTable 表示各变量在局部变量表中的存储情况。
Code:stack=2, locals=2, args_size=20: iconst_2 # 将int型数值2推送到操作数栈顶1: iload_1 # 将第二个int型本地变量推动至栈顶,即将局部变量表中solt索引为1的变量推送至操作数栈栈顶2: imul # 将栈顶两个int类型数值相乘并将结果压入栈顶3: ireturn # 从方法中返回int类型的数据,即将操作数栈栈顶元素弹出
Exceptions属性
Exceptions属性是在方法表中与Code属性平级的一项属性,与Code属性中的异常表不是同一东西,列举出方法中可能抛出的受查异常(Checked Excepitons) , 也就是方法描述时在throws关键字后面列举的异常。
Exceptions属性结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | number_of_exceptions | 1 | 抛出的受查异常种数 |
u2 | exception_index_table | nubmer_of_exceptions | 指向常量池中CONSTANT_class_info型常量的索引 |
LineNumberTable属性
用来描述Java源码与字节码行号之间的对应关系。这里的字节码行号指的是字节码距方法体开始的偏移量。是一个非必须属性。默认生成。可使用-g: none
或-g:lines
来选择或者取消。
若不生成改属性,那么在抛出异常的时候,堆栈中将不会显示出错的行号,在调试时无法按照源码行来设置断点。
LineNumberTable属性结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | line_number_table_length | 1 | 属性个数 |
line_number_info | line_number_table | line_number_table_length | line_number_info集合 |
line_number_info
类型 | 名称 | 备注 |
---|---|---|
u2 | start_pc | 字节码行号 |
u2 | line_number | Java源码行号 |
LocalVariableTable
用来描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,是非必须的。-g:none
,-g: vars
来取消或选择。默认会生成到class文件中,若取消该属性,那么当他人引用时,所有的参数名称都会消失。
LocalVariableTable属性结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | local_variable_table_length | 1 | 本地变量表长度 |
local_variable_info | local_variable_table | local_variable_table_length | 本地变量表 |
local_variable_info结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | start_pc | 1 | 该局部变量的声明周期的开始字节码偏移量 |
u2 | length | 1 | 该局部变量的作用范围 |
u2 | name_index | 1 | 是指向CONSTANT_Utf8_info型常量的索引,代表局部变量的名称 |
u2 | descriptor_index | 1 | 是指向CONSTANT_Utf8_info型常量的索引,代表局部变量的描述符 |
u2 | index | 1 | 在局部变量表中的变量槽位置 |
在JDK引入泛型后增加了**LocalVariableTypeTable **属性,仅仅是将descriptor_index替换为字段的特征签名(Signature) 。
SourceFile及SourceDebugExtension属性
SourceFile属性用于记录生成这个Class文件的源码文件名称。 可选;-g:none
,-g: source
来关闭或开启。若不生成,在抛出异常的时候,堆栈中不会显示出错代码所属的文件名。
SouceFile属性结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | sourcefile_index | 1 | 指向常量池中CONSTANT_Utf8_info型常量的索引, 常量值是源码文件的文件名 |
JDK 5时, 新增了SourceDebugExtension属性用于存储额外的代码调试信息。
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u1 | debug_extension[attribute_length] | 1 | 额外的调试信息 |
ConstantValue属性
该属性的作用是通知虚拟机自动为静态变量赋值。只有被static修饰的变量才能使用这项属性。
非静态变量 | 实例构造器()方法中赋值 |
---|---|
静态变量 | 类构造器()方法中赋值 |
静态变量 | 使用ConstantValue属性 |
Oracle公司的选择是,常量(static final共同修饰的变量)且其类型为基础类型或String,使用ConstantValue属性来进行初始化,非基础类型及字符串,或仅被static修饰那么在()中进行初始化。
ConstantValue属性结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | constantvalue_index | 1 | 常量池中一个字面量的引用 |