声明:
- 本文为学习笔记。
- 本文着重介绍栈帧。
目录
栈(虚拟机栈VM Stack):
堆是存储的单元(堆只保存对象信息),栈是运行时的单位;在整个JVM的内存之中,栈内存是一个非常重要的的概念;栈里面存储的都是与当前线程相关的信息,包括:局部变量、程序运行状态、方法返回地址等。
栈内存是线程私有的,其生命周期和线程相同。
栈描述的是Java方法执行的内存模型;执行一个方法时会产生一个栈帧,随后将其保存到栈(后进先出)的顶部,方法执行完毕后会自动将此方法对应的栈帧自顶部移除(即:出栈),当前方法的栈帧必然在当前线程对应的栈的顶部。
几张与栈相关的概念图:
虚拟机栈:
注:栈中保存的是一个又一个栈帧。
栈帧入栈与出栈:
注:一个栈帧对应一个未运行完的函数;当某一个函数被调用一次时,就会产生一个栈帧(记录着该函数的相
关信息),并入栈;当该函数运行完毕之后,其对应的栈帧会出栈。
注:函数的一次调用就会产生一个对应的栈帧,而不是一个函数本身对应一个栈帧;如:递归调用就会产生
无数个栈帧。
栈的溢出:
关于Stack Overflow Error:
从栈的结构可知:如果栈帧数量过多(n多次调用方法)或某个(些)栈帧过大会导致栈溢出引发SOE(Stack Overflow Error)。
注:如果允许虚拟机栈动态扩展,那么当内存不足时,会导致OOM(OutOfMemoryError)。
栈帧(Stack Frame):
栈帧简述:
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区的虚拟机栈的组成元素。
注:调用一次方法(无论是不是调用的同一个方法)就会产生一个栈帧,可见上面介绍栈时给出的栈帧入栈出栈图。
栈帧中主要包含的数据有:
局部变量表(Local Variable Table):
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
注:局部变量表以变量槽(solt)为最小单位,一个变量槽最大只允许保存四字节(即:32位)长度的变量。如果超过32位,
则会开辟两个连续的solt。
局部变量表储存的数据类型:
数据类型 |
占用slot个数 |
说明 |
boolean |
一个slot |
可以按照Java中的基本数据类型的概念去理解 注:仅仅是理解而已,Java语言与Java虚拟机中的基本数据类型是存在本质差异的。 |
byte |
一个slot |
|
char |
一个slot |
|
short |
一个slot |
|
int |
一个slot |
|
float |
一个slot |
|
long |
两个相邻的slot |
|
double |
两个相邻的slot |
|
reference |
可能一个slot,可能两个slot |
对象实例的引用 注:虚拟机可以通过此引用直接或间接地查找到对象在堆中的数据存放的起始地址索引。 注:虚拟机可以通过此引用直接或间接地查找到在方法区中此对象所属的数据类型。 |
returnAddress |
一个slot |
为字节码指令jsr、jsr_w和ret服务的,指向了一条字节码指令的地址,实现跳转。 注:古老的Java虚拟机使用returnAddress来实现异常水处理,不过现在returnAddress已经被异常表取代了。 |
注:在.java编译为.class文件时,就在方法表的Code属性的max_locals数据项中确定了方法所需要分配的局部
变量表的最大容量。
给出Code属性的结构(以供理解学习):
操作数栈(Operand Stack):
表达式计算在操作数栈中完成。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
注:在概念模型里面,虚拟机栈中的栈帧之间是完全相互独立的。但是在大多数虚拟机的实现里都会做一些优化
处理,令两个栈帧出现一部分重叠。让下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,
这样在进行方法的调用时就可以共用一部分数据,无需进行额外的参数复制传递,重叠的过程如图:
返回地址(Return Adderss):
方法执行完(不论是正常执行还是发生了异常)后需要返回到方法被调用的位置,程序才能继续执行,方法但回事可能需要在栈帧中保存一些信息,用来帮助恢复上层方法的执行状态。
注:一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而
方法异常退出时,返回地址是要通过异常处理器表来决定的,栈帧中一般不会保存这部分信息。
注:方法的退出过程实际上就等于把当前栈帧出栈,因此退出时可能执行的操作有:
- 恢复上层方法的局部变量表和操作数栈。
- 把返回值(如果有的话)压入调用者栈帧的操作数栈中。
- 调整PC计数器的值以指向方法调用指令后面的一条指令。
- ……
动态链接(Dynamic Linking):
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
指向运行时常量池的引用:
当前方法所属的类的运行时常量池的引用,引用其他的常量类或者使用String池中的字符串。