栈内存是程序运行时用于管理函数调用和局部变量的lifo区域,由编译器或jvm自动维护栈帧,含局部变量表、操作数栈、动态链接和方法出口信息;栈存引用不存对象实体,与堆协同实现“栈管谁在用、堆管东西在哪”;栈快因分配释放仅移动指针、无gc、缓存友好,但容量固定且生命周期受限,无法替代堆。

栈内存(Stack)是程序运行时用于管理函数调用和局部变量的一块固定大小、后进先出(LIFO)的内存区域。它的核心特点是“压栈”与“弹栈”——每次方法调用,就压入一个栈帧(Stack Frame);方法返回,就弹出该帧。这个过程由编译器和JVM(或操作系统运行时)自动维护,无需手动干预。
栈帧里存什么?
每个方法调用都会生成一个独立的栈帧,里面包含:
- 局部变量表:存放方法内的基本类型变量(如 int、boolean)、对象引用(注意:不是对象本身,而是指向堆中对象的地址);
- 操作数栈:用于执行字节码指令的临时工作区,比如加法、方法调用前的参数传递都依赖它;
- 动态链接:指向运行时常量池中该方法的符号引用,支持反射和多态;
- 方法出口信息:记录方法执行完后要跳转回哪里(即上层调用者的下一条指令地址)。
压栈是怎么发生的?
压栈不是“把变量一个个推进去”,而是按方法调用顺序,整体分配一个栈帧。例如:
void a() { b(); }void b() { int x = 10; }
→ 执行 a() 时压入帧A → 调用 b() 时在A之上压入帧B → 帧B中为 x 分配局部变量槽(slot)
关键点:
- 局部变量作用域决定其所在栈帧的生命周期,超出作用域不等于立即清理,而是等整个栈帧弹出时一并释放;
- 递归调用会连续压入多个同名方法的栈帧,每帧的局部变量互不干扰;
- 栈空间有限,过深递归或超大局部变量数组容易触发 StackOverflowError。
栈和堆怎么配合?
栈不存对象实体,只存引用。例如:String s = new String("hello");
- "hello" 字符串字面量存在**方法区/元空间**(JDK 8+);
- new String("hello") 在**堆**中创建新对象;
- 变量 s 本身(即那个引用值)存在当前方法的**栈帧局部变量表**中。
也就是说:栈管“谁在用”,堆管“东西在哪”。方法结束 → 栈帧弹出 → 引用消失 → 堆中对象若无其他引用,就变成垃圾等待回收。
为什么栈快?为什么不能全用栈?
栈快,因为:
- 内存连续,分配只需移动栈顶指针(push),释放只需回退指针(pop);
- 无碎片问题,无GC开销;
- CPU缓存友好,访问局部性高。
但不能全用栈,因为:
- 栈空间小且固定(通常几MB),无法容纳大对象或长期存活的数据;
- 栈帧随方法退出自动销毁,无法支持跨方法共享或动态生命周期的对象;
- 对象可能被多个引用持有,栈的LIFO结构无法匹配这种任意生命周期需求。









