属性表集合
1、属性表概述
属性表可以出现再类文件、字段表、方法表之后,用于修饰特定的场景。目前属性表有21种类型,每种属性表的具体组成都不同,但都符合这样的一个整体结果:
类型 |
名称 |
含义 |
u2 |
Attribute_name_index |
属性表名称索引,对应常量池一个utf-8类型的常量 |
u4 |
Attribute_length |
属性值的字节长度,标识后续多少个字节是属性值内容 |
u1 |
Info |
属性值 |
即每个属性表由:属性名称+属性值字节长度+属性值构成。不同属性表是由属性名称确定。目前Java规范定义的21种属性表如下所示:
属性名称 |
使用位置 |
含义 |
Code |
方法表 |
Java代码编译成的字节码指令 |
ConstantValue |
字段表 |
Final关键字定义的常量值 |
Deprecated |
类、方法表、字段表 |
被声明为deprecated的方法和字段 |
Exceptions |
方法表 |
方法抛出的异常 |
EnclosingMethod |
类文件 |
当一个类为局部类或匿名类时拥有这个属性,标识这个类所在的外围方法 |
InnerClass |
类文件 |
内部类列表 |
LineNumberTable |
Code属性 |
Java源码行号和字节码指令的对应关系 |
LocalVariableTable |
Code属性 |
方法局部变量描述 |
StackMapTable |
Code属性 |
用于检查模板方法的局部变量和操作数栈所需类型是否匹配 |
Signature |
类、方法表、字段表 |
泛型签名信息 |
SourceFile |
类文件 |
源文件名称 |
SourceDebugExtension |
类文件 |
存储额外的调试信息,比如定位jsp文件行号 |
Synthetic |
类、方法表、字段表 |
标注是否为编译器自动生成 |
localVariableTypeTable |
类 |
使用特征签名代替描述符,能够再引入泛型语法之后描述泛型参数化类型 |
RuntimeVisibleAnnotations |
类、方法表、字段表 |
支持动态注解,指明运行时可见的注解 |
RuntimeInvisibleAnnotations |
类、方法表、字段表 |
与上一个相反,指明运行时不可见的注解 |
RuntimeVisibleParameterAnnotations |
方法表 |
指明运行时可见的方法参数 |
RuntimeInvisibleParameterAnnotations |
方法表 |
指明运行时不可见的方法参数 |
AnnotaionDefault |
方法表 |
记录默认注解类元素的默认值 |
BootstrapMethods |
类文件 |
保存invokedynamic指令引用的引导方法限定符 |
接下来,具体讲解几种常见的属性表:
2、code属性表
Code属性是class文件中最重要的一个属性,它记录了java方法代码转化之后对应的字节码指令。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_count |
1 |
Exception_info |
Exception |
Exception_table_count |
u2 |
Attributes_count |
1 |
Attribute_info |
Attributes |
Attributes_count |
- l Attribute_name_index是指向常量池中一个CONSTANT_UTF_8类型常量的索引
- l Attribute_length是接下来属性值占的字节数
- l Max_stack最大操作数栈深度。这里需要了解下,java中方法调用执行时的内存模型-栈帧,是一个虚拟机栈的栈元素,一个栈帧对应一个方法。一个栈帧中包含局部变量表、操作栈以及附加信息。其中操作数栈是一个后入先出栈,初始为空,在方法执行过程中,字节码指令会往操作数栈中写入和提取内容。栈的最大深度可以在编译期间知道,并通过max_stack记录,在方法栈帧入栈的时候即可分配正确的操作数栈深度。
- l Max_locals最大局部变量表大小。方法内存模型中局部变量表用于保存方法参数(包括实例方法的隐含参数this)、try…catch异常、局部变量,而这些是可以在编译期确定的。局部变量表的基础单位是容量槽(slot),代表能存储boolean、byte、char、short、int、float、reference或returnAddress类型的数据,而对于long和double数据类型,则需要相邻的两位slot存储。同时slot可重用,当指令计数器超过局部变量的作用域时,那这个变量的slot可以分配给其他变量。所以max_locals并不是简单的累加获得。
- l Code_length和code,字节码指令长度和紧随其后的字节码指令流。Java虚拟机中指令长度为一个字节,字节指令取值为0x00~0xFF,目前总共定义了约200条指令。举例字节码指令: 0x02助记符为iconst_0代表 将int型1推送至栈顶;0xb7助记符为invokespecial,代表调用父类构造方法或实例初始化方法或私有方法,方法的调用对象为当前栈顶的reference数据类型所指的对象,具体哪个方法名则由跟在字节码指令后的索引参数指定常量池中的一个CONSTANT_Methodref_info常量指定。Java子集码指令可参考https://blog.csdn.net/wangxf_8341/article/details/50402525?utm_source=blogxgwz0
- l Exceptio_table_count和exception,java使用异常表来记录try…catch的异常跳转,异常表结构如下:
类型 |
名称 |
数量 |
u2 |
Start_pc |
1 |
u2 |
End_pc |
1 |
u2 |
Handler_pc |
1 |
u2 |
Catch_type |
1 |
解释为从方法的第start_pc处指令到end_pc处指令若发生catch_type类型(指向常量池一个CONSTANT_Class_info类型)则跳转到handler_pc处指令。
- l Attributes_count和attributes属性表集合则是记录了针对code属性的属性表集合。
※举例方法的code字节码指令实例:
Public int inc(){ Int x; try{ x=1; return x; }catch(Exception e){ x=2; return x; }finally{ x=3 ; } } |
使用javap –verbose XXX.class命令获得字节码指令如下:
这段代码有三种执行结果:
- 若没有异常,返回值为1;
- 若出现exception异常,返回值为2;
- 其他异常,非正常退出无返回值。
3、ConstantValue属性表
在Sun javac编译器中,当一个字段有fianl、static修饰符,且为基本数据类型或String时,则会生成ConstantValue对字段进行初始化。只有static修饰符或有final、static但不为基本类型和String,则会在类初始化方法<clinit>中初始化,无static修饰则会在实例初始化方法<init>中初始化。ConstantValue属性表结构如下:
类型 |
名称 |
数量 |
u2 |
Attribute_name_index |
1 |
u4 |
Attribute_length |
1 |
u2 |
Constantvalue_index |
1 |
其中constantvalue_index是一个常量池索引,指向常量池中的常量值。