栈帧是函数调用时在栈上分配的独立存储单元,用于存放局部变量、参数、操作数栈和返回地址,遵循后进先出原则;递归调用会持续创建新帧,栈深度与帧大小共同决定栈空间占用,超出限制则触发StackOverflowError等异常。

栈帧是函数调用的“临时工位”
每次函数被调用,JVM(或JS引擎、C++运行时)都会在当前线程的栈上分配一个栈帧,用来存放:局部变量、参数、操作数栈、返回地址等。这个帧不是共享的——每调用一次,就新建一帧;每返回一次,就销毁一帧。它像一层层叠放的纸盒,后进先出。递归调用时,同一函数反复压入新帧,比如 factorial(5) 会依次生成 factorial(5)→factorial(4)→factorial(3)→factorial(2)→factorial(1)→factorial(0) 共6个栈帧,全部驻留在栈中直到开始回退。
递归层级直接决定栈空间占用量
栈大小是操作系统和运行时预设的有限资源(Java默认约1MB/线程,JS引擎通常几百KB)。栈帧数量 × 单帧平均大小 = 实际栈消耗。而单帧大小受两方面影响:
- 函数内声明的局部变量数量与类型(如大数组、对象引用会略增开销)
- 参数个数与体积(基本类型影响小,大对象引用本身不占多,但可能触发额外逻辑)
所以,哪怕每个帧只占1KB,1000层递归就需约1MB栈空间——刚好触达Java默认阈值。若局部变量较多或平台默认栈更小(如嵌入式环境),几百层就可能溢出。
终止条件缺失或失效,会让栈帧无限堆积
没有基例(base case),或基例逻辑错误(如 if (n == 1) 却传入负数),递归就不会停止。此时栈帧持续压入,无一弹出,最终耗尽所有可用栈空间,抛出 StackOverflowError(Java)、RangeError: Maximum call stack size exceeded(JS)等异常。这类错误不是内存泄漏,而是同步调用链失控的即时表现——它发生得快、定位准、但后果确定。
非递归因素也会加剧栈压力
递归只是最典型的诱因,但以下情况同样推高栈帧总量:
- 深度嵌套的普通方法调用(如 A→B→C→D→…→Z,共50层)
- 每个方法都声明了大型局部数组(如
int[] arr = new int[100000]) - 匿名内部类或Lambda捕获大量外部变量,间接增大栈帧体积
- 某些框架的拦截器链或AOP增强层层包裹,隐式加深调用栈
这些场景下,即使没有显式递归,栈仍可能在“不知不觉”中被填满。










