垃圾收集器与内存分配策略( 二 )


字段表
字段表()用于描述接口或者类中声明的变量 。Java语言中的“字段”(Field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量 。字段可以包括的修饰符有字段的作用域(、、修饰符)、是实例变量还是类变量(修饰符)、可变性(final)、并发可见性(修饰符,是否强制从主内存读写)、可否被序列化(修饰符)、字段数据类型(基本类型、对象、数组)、字段名称 。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示 。而字段叫做什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述 。
方法表
Class文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式,方法表的结构如同字段表一样,依次包括访问标志()、名称索引()、描述符索引()、属性表集合()几项
属性表
Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息 。对于每一个属性,它的名称都要从常量池中引用一个类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可 。一个符合规则的属性表应该满足表中所定义的结构 。
属性表集合 - Code 属性
Java程序方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内 。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口或者抽象类中的方法就不存在Code属性,如果方法表有Code属性存在,那么它的结构将如表
是一项指向型常量的索引,此常量值固定为“Code”,它代表了该属性的属性名称,指示了属性值的长度,由于属性名称索引与属性长度一共为6个字节,所以属性值的长度固定为整个属性表长度减去6个字节 。
代表了操作数栈( Stack)深度的最大值 。在方法执行的任意时刻,操作数栈都不会超过这个深度 。虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度( 异常) 。
代表了局部变量表所需的存储空间 。在这里,的单位是变量槽(Slot),变量槽是虚拟机为局部变量分配内存所使用的最小单位 。对于byte、char、float、int、short、和等长度不超过32位的数据类型,每个局部变量占用一个变量槽,而和long这两种64位的数据类型则需要两个变量槽来存放 。方法参数(包括实例方法中的隐藏参数“this”)、显式异常处理程序的参数(,就是try-catch语句中catch块中所定义的异常)、方法体中定义的局部变量都需要依赖局部变量表来存放 。注意,并不是在方法中用了多少个局部变量,就把这些局部变量所占变量槽数量之和作为的值,操作数栈和局部变量表直接决定一个该方法的栈帧所耗费的内存,不必要的操作数栈深度和变量槽数量会造成内存的浪费 。Java虚拟机的做法是将局部变量表中的变量槽进行重用,当代码执行超出一个局部变量的作用域时,这个局部变量所占的变量槽可以被其他局部变量所使用,Javac编译器会根据变量的作用域来分配变量槽给各个变量使用,根据同时生存的最大局部变量数量和类型计算出的大小 。
和code用来存储Java源程序编译后生成的字节码指令 。代表字节码长度,code是用于存储字节码指令的一系列字节流 。既然叫字节码指令,那顾名思义每个指令就是一个u1类型的单字节,当虚拟机读取到code中的一个字节码时,就可以对应找出这个字节码代表的是什么指令,并且可以知道这条指令后面是否需要跟随参数,以及后续的参数应当如何解析 。