JVM之Class结构属性表
概述
上篇文章提到过在Class结构表中,属性表存在于Class表,字段表和方法表中,是为了描述额外的信息。
属性表属性总览
这张图中按Class结构,字段表,方法表这三个维度进行了区分标注,将三者共有的属性提取到最顶部的黄色椭圆中,蓝色代表各自属性表额外用到的属性,红色代表Code属性中引用的其他属性的集合。
引自《深入理解JAVA虚拟机》,读者也可以看这下面的两张图:
属性结构
首先表结构中的前两个字节说明该属性是叫什么名字,也就是什么类型的属性,最终指向常量池中的CONSTANT_Utf8_info类型的常量。
接着用四个字节描述属性值的长度,也就是说明属性值所占用的字节数;
最后列出属性信息,有多少属性信息呢?前四个字节已经列出了这个属性包含多少个属性,因此最后描述各个属性的信息。
前两个属性是所有属性都共有的,之后就不进行讲解了。
常见属性
Code
该属性用于存放
操作数栈最大深度,
本地变量表最大占用存储空间,
方法中Catch块定义的异常类型和数量,
编译后方法体的字节码指令和指令长度,
还有其他属性
首先先看下Code属性表的结构:
1.max_stack操作数栈最大深度
先看下之前的这篇文章,Java程序运行是基于栈的操作,就是说的该项属性。
通过指令从本地变量表中拿数据放到栈顶;将数据从栈顶保存到本地变量中;对栈顶两个值进行运算后将结果压入栈顶;将栈顶的值作为返回结果return…等等这些都是通过操作数据结构栈来完成的。
2.max_locals局部变量表所需空间
局部变量表最大占用的存储空间,存储的单位用的是Slot(变量槽),一个
Slot占用空间大小为32位。
存储范围:
比如方法的参数,Catch块中定义的异常类型,方法体中定义的局部变量。
存储空间:
对于小于32位的数据类型用一个变量槽,大于32位的用多个变量槽存储(比如小于64位的数据类型double和long用两个变量槽存储)。
优化:
对变量限定作用域,如果变量超出了作用域范围,那么存储该变量的Slot进行存储其他的变量,也就是重用变量槽。
3.code_length,code属性用于表述方法体编译后的字节码指令长度和字节码指令流
4.exception_table_length,exception_table用于描述方法块中Catch块定义的异常数量和类型
Exceptions
该属性和上面提到的exception_table描述的信息不一样,exception_table是描述方法体中Catch块中定义的异常数量和类型;而该项属性是描述方法throws的异常数量和类型,通俗来说就是调用该方法需要catch的异常,也叫作受查异常。
结构:
LineNumberTable
结构
LocalVariableTable,LocalVariableTypeTable
1.LocalVariableTable
2.LocalVariableTypeTable
结构:
ConstantValue
变量初始化,赋值时机:
位于该属性结构中的常量将会在类加载的准备阶段就会初始化并且赋值;
其他的静态变量在这个阶段只是会被初始化然后赋默认值,如果静态变量设置了final关键字,那么就是第一种情况会对变量进行赋值;
对于实例变量(非静态变量)的赋值是在实例构造器《init》中。
该结构中存放的字段是:
《Java虚拟机规范》中规定该属性结构中存放的必须是静态的字段,而对于javac编译器来说还需要满足final关键字的修饰,因此经过javac编译器编译后的该属性中存放的字段必须是static并且是final的。
结构:
Deprecated及Synthetic属性
1.Deprecated属性
该属性用于表示某个类,字段或方法已经不再推荐使用,通过“@deprecated”注解设置这个属性
2.Synthetic属性
该属性用于表示字段或者方法是编译器自己添加的,不是代码中的。
也可以通过设置访问标志ACC_SYNTHETIC标志位生成该项属性。
结构:
两者都一样,不携带任何属性值。只是用于标识
StackMapTable
该属性位于Code属性的属性表中
结构:
一个Code属性最多只能有一个StackMapTable属性
MethodParameters
用于记录方法的各个形参名称和信息
方法参数属性,位于class中的属性表中。
之前说过这部分是存储在局部变量表中的,因为方法中有方法体code属性,而code中需要有局部变量表属性代表这个方法中的变量存储。
但是为什么还要单独抽出一个属性放在class中呢?
大家想想没有code就没有局部变量表,没有局部变量表是不是就不能存储方法参数了;你看接口中他有方法吧但是呢他其实没有方法提code所以它的方法参数往哪放呢?往他借口的属性表集合中放,也就是与code同级。这样的话我接口里可以直接获得方法参数通过这个属性;而对于正常的方法也就是有方法体的代码可以从code中的局部变量表中拿。
数据结构:
1.首先说明他是什么,我是一个方法参数类型
2.我说明我存储的时候数据有多长(多少字节),为了切割按照这个就可以正确读取对应的数据;但是如果这个属性中还用到了其他的数据结构(属性),那么就是这个属性的个数了
3.对于没有再次用到其他属性来描述的属性直接使用定长数据即可;但是对于有用到其他属性来描述这个属性的话,则后面是对应的属性一个一个排开,然后每个属性如果是定长的话则不需要通过长度来说明所占字节,然后这个属性中存储的第一个永远是他是什么也就是名字,然后再是对应的值
不断使用这种结构来描述一个完整的class结构
结构: