局部变量表(Local Stack Frame):
是一种程序运行数据模型,存放了编译期可知的各种数据类型例如:Boolean、byte、char、short、int、float、long、double、对象引用类型(对象内存地址变量,指针或句柄),程序运行时,根据局部变量表 分配栈帧空间大小,在运行中,大小是不变的。是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。并且在Java编译为Class文件时,就已经确定了该方法所需要分配的局部变量表的最大容量。
局部变量表是有索引的,就像数组一样。从0开始,到表的最大索引,也就是Slot的数量-1。方法参数的个数 + 局部变量的个数 ≠ Slot的数量。因为Slot的空间是可以复用的,当pc计数器的值已经超出了某个变量的作用域时,下一个变量不必使用新的Slot空间,可以去覆盖前面那个空间。
在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法,那局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用。(在方法中可以通过关键字this来访问到这个隐含的参数)。其余参数则按照参数表顺序排列,占用从1开始的局部变量Slot。
变量槽(Variable Slot):
局部变量表的容量以变量槽为最小单位,每个变量槽都可以存储32位长度的内存空间,例如boolean、byte、char、short、int、float、reference。对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写。
reference(对象实例的引用)一般来说,虚拟机都能从引用中直接或者间接的查找到对象的以下两点 :
①在Java堆中的数据存放的起始地址索引。
②所属数据类型在方法区中的存储的类型数据。
Slot复用:
为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的,也就是说当PC计数器的指令指已经超出了某个变量的作用域(执行完毕),那这个变量对应的Slot就可以交给其他变量使用。
优点 : 节省栈帧空间。
缺点 : 影响到系统的垃圾收集行为。(如大方法占用较多的Slot,执行完该方法的作用域后没有对Slot赋值或者清空设置null值,垃圾回收器便不能及时的回收该内存。(弱引用,垃圾回收扫描)
public static void main(String [] args){
byte[] placeholder = new byte[64*1024*1024];
System.gc();
}
[GC (System.gc()) 68198K->66320K(125952K), 0.0016640 secs]
[Full GC (System.gc()) 66320K->66177K(125952K), 0.0079881 secs]
第一行,Allocation Filure(空间分配失败)引起了Minor GC。因为创建的对象太大,新生代装不下,所以进行了一次GC。
第二行,由于新生代GC完了后,还是装不下,这时就应该把它直接放到老年代,为了老年代又足够的空间来迎接这个大对象,所以老年代进行一次Full GC。
动态连接(动态解析/静态解析)
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支付方法调用过程中的动态连接(Dynamic Linking)。(运行动态解析)
在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。另外的一部分将在每一次运行时期转化为直接引用。这部分称为动态连接。(关于这部分,后面会再继续分析)(类加载静态解析连接)
一个方法调用另一个方法,或者一个类使用另一个类的成员变量时,总得知道被调用者的名字吧?(你可以不认识它本身,但调用它就需要知道他的名字)。符号引用就相当于名字,这些被调用者的名字就存放在Java字节码文件里。
名字是知道了,但是Java真正运行起来的时候,真的能靠这个名字(符号引用)就能找到相应的类和方法吗?
需要解析成相应的直接引用,利用直接引用来准确地找到。
举个例子,就相当于我在0X0300H这个地址存入了一个数526,为了方便编程,我把这个给这个地址起了个别名叫A, 以后我编程的时候(运行之前)可以用别名A来暗示访问这个空间的数据,但其实程序运行起来后,实质上还是去寻找0X0300H这片空间来获取526这个数据的。
这样的符号引用和直接引用在运行时进行解析和链接的过程,叫动态链接。
动态链接的前提:每一个栈帧内部都要包含一个指向运行时常量池的引用,来支持动态链接的实现。
操作数栈
操作数栈和局部变量表一样,在编译时期就已经确定了该方法所需要分配的局部变量表的最大容量。操作数栈的每一个元素可用是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型占用的栈容量为2。
当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈 / 入栈操作。
例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其它方法的时候是通过操作数栈来进行参数传递的。
索然两个栈帧作为虚拟机栈的元素是完全独立的,但是虚拟机会做出相应的优化,令两个栈帧出现一部分重叠。
如上图所示,栈帧的部分操作数栈与上一个栈帧的局部变量表重叠在一起,这样在进行方法调用时就可以共用一部分数据,无须进行额外的参数复制传递。