虚拟机栈(Java Virtual Machine Stacks)
一、基础定义
- 线程私有:每个线程独占一份虚拟机栈,生命周期和线程同步;
- 存储内容:存放方法调用过程,遵循先进后出;
- 组成单元 :每一次方法调用都会创建一个栈帧(Frame),方法执行完毕栈帧出栈释放;
- 活动栈帧:同一线程同一时刻只有顶部栈帧为活动栈帧,对应当前正在执行的方法。
二、栈帧内部存储内容
- 局部变量表
存放方法参数、方法内局部变量(基本类型、对象引用地址)。 - 操作数栈
字节码运算临时缓冲区,执行加减、对象创建、方法传参时临时存放数据。 - 动态链接(运行时常量池引用)
指向方法区常量池符号引用,运行时解析为实际方法地址。 - 方法返回地址
方法执行结束后,回到调用处的字节码行号,正常返回/异常返回都依靠该地址恢复执行。 - 附加信息(可选):异常表、附加调试信息。
三、高频面试问题
1. 垃圾回收是否处理栈内存?
GC主要回收堆内存;栈内存不需要GC,方法执行完成栈帧直接出栈,内存自动释放。
2. 栈内存越大越好吗?
不是,默认栈大小一般1024K(-Xss参数调整)。
单栈内存设置越大,机器整体能创建的并发线程数量越少,会降低服务并发承载能力。
3. 方法局部变量是否线程安全?
- 局部对象不逃出方法作用域:线程安全。每个线程栈帧独有局部变量,无共享竞争;
- 局部对象通过参数传递/return返回,逃逸到其他线程:线程不安全,多线程并发修改会出现数据错乱。
示例:
java
// 安全:sb仅当前栈帧使用,不对外暴露
public void m1(){
StringBuilder sb = new StringBuilder();
sb.append("123");
}
// 不安全:sb引用传递给其他线程共享
public void m2(StringBuilder sb){
sb.append("456");
}
// 不安全:将局部对象返回,外部多线程共用
public StringBuilder m3(){
StringBuilder sb = new StringBuilder();
return sb;
}
4. 栈内存溢出 StackOverflowError 两种场景
- 递归调用无终止条件,创建大量栈帧,栈深度超限;
java
public void m4(){
m4(); // 无限递归,抛出StackOverflowError
}
- 单个栈帧局部变量、操作数栈占用过大,单帧超过栈容量。
四、堆与虚拟机栈核心区别
- 归属:栈是线程私有;堆是全线程共享。
- 存储:栈存局部变量、方法调用栈帧;堆存所有对象、数组实例。
- 回收:堆依靠GC回收;栈方法结束自动出栈释放,无GC。
- 溢出异常:
- 栈不足:
java.lang.StackOverflowError - 堆不足:
java.lang.OutOfMemoryError: Java heap space
五、面试简答
问:虚拟机栈里的栈帧存放什么?
答:
每个方法调用对应一个栈帧,栈帧包含五部分:局部变量表、操作数栈、动态链接、方法返回地址、附加信息。
局部变量表存方法参数与局部变量;操作数栈用于字节码运算临时存数据;动态链接指向常量池解析方法;返回地址记录调用处位置,方法结束后恢复上层代码执行。