《JVM第5课》虚拟机栈
Java虚拟机栈(Java Virtual Machine Stack,简称JVM栈,又称Java方法栈)是 JVM 运行时数据区的一部分,主要用于支持Java方法的执行。每当一个新线程被创建时,JVM就会为这个线程分配一个私有的方法栈,线程执行过程中每个方法调用都会创建一个新的栈帧(Stack Frame),而这些栈帧会被组织成后进先出的栈结构。
1 栈知识点
-
线程私有
- 每个线程都有自己的虚拟机栈,与其他线程的栈完全隔离。
- 线程私有的特性确保了多线程环境下的数据安全和并发执行的效率。
-
生命周期与线程相同
- 虚拟机栈的生命周期与线程的生命周期相同。当线程创建时,虚拟机栈也随之创建;当线程结束时,虚拟机栈也随之销毁。所以虚拟机栈不需要进行垃圾回收。
-
栈深度
- 如果方法调用层次过深或者循环调用导致栈帧数量过多,可能会发生栈溢出错误
StackOverflowError
。但如果线程太多,线程创建时没有足够的内存去创建虚拟机栈,则会抛出OutOfMemoryError
错误。 - 虚拟机栈的深度没有明确的个数限制(一般1、2千没什么问题),而是受栈大小的影响,通常通过JVM启动参数 -Xss 来设置每个线程的栈大小(默认是1M)。合理配置栈大小可以避免栈溢出和内存不足的问题。
- 如果方法调用层次过深或者循环调用导致栈帧数量过多,可能会发生栈溢出错误
-
栈帧(Stack Frame)
- 每个方法调用都会创建一个新的栈帧,并将其压入当前线程的虚拟机栈顶。
- 当方法执行完毕后,对应的栈帧会被弹出并丢弃。
- 栈帧是方法执行的基本单位,包含局部变量表、操作数栈、动态链接、方法出口信息等。
2 栈帧
每个栈帧包含了以下部分:
- 局部变量表:用于存放方法参数和方法内部定义的局部变量。局部变量表以变量槽(Variable Slot)的形式存在,每个槽可以存放一个基本数据类型、返回地址或者对象引用。对于64位的数据类型(如long和double),需要占用两个连续的槽位。
- 操作数栈:顾名思义,就是用来暂存要操作的数据和操作结果的地方。当执行字节码指令时,会从局部变量表或常量池中加载数据到操作数栈,然后根据指令完成相应的计算或操作,最后将结果存回操作数栈或局部变量表。
- 动态链接:包括对常量池的引用和对上层方法栈帧的引用,用于支持方法调用过程中的动态链接。
- 方法出口信息:包含正常完成出口和异常完成出口的信息,用于处理方法执行完毕后的返回值传递和异常抛出。
当一个方法被调用时,JVM会执行如下步骤:
- 创建栈帧:为被调用的方法创建一个新的栈帧,并将其压入当前线程的方法栈顶。
- 初始化栈帧:将方法参数传递到新的栈帧的局部变量表中,同时清空操作数栈。
- 执行字节码指令:根据方法体中的字节码指令,进行相应的计算和逻辑处理。
- 清理栈帧:当方法执行完毕后(无论是正常结束还是异常终止),将结果(如果有)返回给上一层方法,然后从方法栈中弹出当前栈帧。