Java字节码结构剖析四:属性表

了解了方法表的结构后,我们知道方法表有自己的属性表,用来存储与当前方法相关的附加属性。属性表每一个成员的值必须是attribute结构(如下所示:),一个方法可以有任意个与之相关的属性。

attribute_info {
	u2 attribute_name_index;
	u4 attibute_length;
	u1 info[attibute_length]
}

JVM规范所定义的 method_info结构中,属性表可出现的成员有:CodeExceptionsSyntheticSignatureDeprecateduntimeVisibleAnnotationsRuntimeInvisibleAnnotations
RuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotationsAnnotationDefault结构。

Code属性

这么多属性中,Code是很常见的也是很重要的一个属性。所以,了解这个属性很有必要。

Code 属性是一个变长属性,位于 method_info结构的属性表。一个 Code属性只为唯一一个方法、实例类初始化方法或类初始化方法保存 Java 虚拟机指令及相关辅助信息。 所有 Java 虚拟机实现都必须能够识别 Code属性。如果方法被声明为 native或者abstract类型,那么对应的 method_info结构不能有明确的 Code 属性,其它情况下,method_info有必须有明确的 Code 属性。

Code属性的格式如下:

Code_attribute {
	u2 attribute_name_index;
	u4 attribute_length;
	u2 max_stack;
	u2 max_locals;
	u4 code_length;
	u1 code[code_length];
	u2 exception_table_length;
	{  u2 start_pc;
	   u2 end_pc;
	   u2 handler_pc;
	   u2 catch_type;
	} exception_table[exception_table_length];
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}

Code_attribute结构各项的说明如下:

  • attribute_name_index
    attribute_name_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_Utf8_info结构,表示字符串“Code”。
  • attribute_length
    attribute_length表示attribute所包含的字节数,不包含attribute_name_indexattribute_length字段。
  • max_stack
    max_stack表示这个方法运行的任何时刻所能达到的操作栈的最大深度。
  • max_locals
    max_locals项的值给出了分配在当前方法引用的局部变量表中的局部变量个数,包括调用此方法时用于传递参数的局部变量。
  • code_length
    code_length项给出了当前方法的 code[]数组的字节数,即表示该方法所包含的字节码的字节数。
  • code[]
    code[]数组,存放具体的字节码即时该方法被调用时,虚拟机所执行的字节码。
  • exception_table_length
    exception_table_length项的值给出了 exception_table[]数组的成员个数量。
  • exception_table[]
    • exception_table[]这里存放的是处理异常的信息。
    • 每个exception_table表项由start_pcend_pchandler_pccatch_type组成。
    • start_pcend_pc表示在Code数组中的从start_pcend_pc处(包含start_pc,不包含end_pc)的指令抛出异常会由这个表项来处理。
    • handle_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有异常。
    • attributes_countattributes[]自然又构成了Code属性的附加属性。可以出现在Code属性的属性表中的成员只能是 LineNumberTableLocalVariableTableLocalVariableTypeTableStackMapTable属性。

代码示例

在上一篇《方法表》里,我分析了<init>构造方法。其中方法的Code属性,在字节码里的展示如下所示(阴影部分):
构造方法的Code属性的16进制表示
先简单介绍一下Code属性的16进制结构:0x0013=19,查询常量池索引19的位置得到属性的名字Code。然后是4个字节的attribute_length,0x00000042=66,表示存储这个Code属性内容的字节数组的长度为66。所以往后再数66个字节结束,就得到<init>方法完整的Code属性信息了。也就是图上阴影部分所示。那这个Code属性内容是什么呢?这里可以借助小工具jclasslib bytecode viewer。如下图所示:
在这里插入图片描述
有了jclasslib bytecode viewer这个工具,查看字节码内容就很清晰了,跟我们分析得一模一样。而且它还把属性内容里的指令翻译成助记符了,我们能清晰的看到这个<init>方法里的java代码在编译成字节码后是什么样子的。

根据Code属性的结构,attribute_length之后,0x0002=2,自然代表操作栈的最大深度为2。紧接着,0x0001=1,表示局部变量表的长度为1。code_length是0x00000010=16,也就是存放具体的字节码的Code[]长度为16。这个Code[]里的内容就是上面看到的,jclasslib bytecode viewer展示给我们看到的助记符了。

我们就往后数16个字节,来到了该方法的异常表处,首先是exception_table_length异常表的长度,0x0000=0,即长度为0,就是异常表为空,没有异常表的意思。显然我们的构造方法没有声明抛出异常。所以这里的就没有exception_table[]的信息了。

那么紧接其后的就是 attributes_countattributes[]Code属性的属性表。0x0002=2,表示有2个属性。往后看,0x0014=20,查常量池该位置,得到LineNumberTable字符串表示。所以第一个属性就是LineNumberTable(行号表)。这个属性用来表示Code数组中的字节码和Java代码行数之间的关系。这个属性可以用来在调试时候定位代码执行的行数。

LineNumberTable属性格式如下:

LineNumberTable_attribute {
	u2 attribute_name_index;
	u4 attribute_length;
	u2 line_number_table_length;
	{   u2 start_pc;
		u2 line_number;
	} line_number_table[line_number_table_length];
}

有了这个结构,我们就能找到第二个属性的名字就是,接着往后数0x0000000E=14个字节。定位到,0x0015=21,常量池查询得到,第二个属性的名字是LocalVariableTable(局部变量表)。用来描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,非运行时必须属性。
其结构如下:

LocalVariableTable_attribute {
	u2 attribute_name_index;
	u4 attribute_length;
	u2 local_variable_table_length;
	{   u2 start_pc;
		u2 length;
		u2 name_index;
		u2 descriptor_index;
		u2 index;
	} local_variable_table[local_variable_table_length];
}

其实通过jclasslib bytecode viewer我么可以很直观的看到LineNumberTableLocalVariableTable属性的内容。如下:
LineNumberTable 内容
LocalVariableTable

总结

  • 通过学习Code属性,了解到我们编写的Java代码,在字节码中是如何存储的。
  • 我们在用idea进入debug模式的时候,因为有LineNumberTable属性,所以可以轻松地实现字节码里的指令和我们Java代码行号对应上。
  • LocalVariableTable属性,如果没有这个属性,最大的影响就是当其他人引用这个方法时,所有参数名称都将会丢失,IDE就会使用诸如arg0、arg1之类的占位符代替原有的参数名,这对程序运行没有影响,但是会对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获得参数值。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值