- 虚拟机栈描述的是java方法执行的动态内存模型
- 栈帧
- 每个方法执行,都会创建一个栈帧,伴随着方法从创建到执行完成。
- 栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等
- 局部变量表
- 存放编译器可知的各种基本数据类型,引用类型,returnAddress类型
- 局部变量表的内存空间在编译器完成分配,当进入一个方法时,这个方法需要在帧中分配多少内存是固定的,在方法运行期间是不会改变局部变量表的大小(为什么是固定的,因为栈中存的是对象的引用,而非对象本身)
- 操作栈
-
同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
举个例子,整数加法的字节码指令iadd在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈。
-
- 动态链接
- 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接
- 方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(重写方法),暂时还不涉及方法内部的具体运行过程。
- Class 文件中存放了大量的符号引用,字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
大白话:静态连接就是类加载阶段就将常量池中的符号引用转化成直接引用,动态连接正好相反,就是为了支持运行期间的常量池中的符号引用能够转换成直接引用
- 方法出口
- 当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理。
- 方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以指向常量池的引用作为参数
- 部分符号引用在类加载阶段(解析)的时候就转化为直接引用,这种转化为静态链接
- 部分符号引用在运行期间转化为直接引用,这种转化为动态链接
压栈出栈图:和弹夹一样,先进后出,方法的调用亦是如此,结果一层一层往外返回
当栈的空间被占满了会怎么样?
利用递归调用方法,不断压栈来试一试
显然程序爆出了 StackOverflowError 栈内存溢出的异常
java本地方法栈和虚拟机栈结构相同
不同的是:
- 虚拟机栈为虚拟机执行java方法服务
- 本地方法栈为虚拟机执行native方法服务
HotSpot虚拟机将二者合二为一统称为栈
栈内存大小设置
栈内存为线程私有的空间,每个线程都会创建私有的栈内存。栈空间内存设置过大,创建线程数量较多时会出现栈内存溢出StackOverflowError。同时,栈内存也决定方法调用的深度,栈内存过小则会导致方法调用的深度较小,如递归调用的次数较少。
配置关键字:-Xss 如-Xss128k