为何Java中斐波那契递归未触发StackOverflowError?
为什么递归阶乘触发StackOverflowError但斐波那契却没有?
我最近测试Java的StackOverflowError时写了这段代码:
package recursion_still_not_working; public class Main { public static void main(String[] args) { // System.out.println(fibonacci(50)); System.out.println("Result: " + factorial(3000)); } public static long fibonacci(long n) { if (n > 1) { //System.out.println("calculating with " + (n - 1) + " + " + (n - 2)); return fibonacci(n - 1) + fibonacci(n - 2); } else { return n; } } public static long factorial(long n) { if (n > 1) { System.out.println("calculating with " + n); return n * factorial(n - 1); } System.out.println("base case reached: " + n); return n; } }
我本来以为调用fibonacci(50)和factorial(3000)都会触发栈溢出,但实际只有阶乘方法报错,斐波那契却能正常运行。我猜测是不是JVM做了什么隐藏优化,对斐波那契递归生效但阶乘无效?我的JVM版本是:openjdk 11.0.3 2019-04-16 OpenJDK Runtime Environment (build 11.0.3+7-Ubuntu-1ubuntu218.04.1) OpenJDK 64-Bit Server VM (build 11.0.3+7-Ubuntu-1ubuntu218.04.1, mixed mode, sharing)
嘿,这个问题其实核心点你可能没注意到——两种递归的实际栈深度差得太远了!
先给你拆解清楚:
- 你调用的
factorial(3000)是一条“直线型”递归:从3000一路调用到1,每一层调用都会在栈里留下一个栈帧,直到最后才开始释放。这一下就堆了3000个栈帧,直接超出了JVM默认栈的承受范围。 - 而
fibonacci(50)虽然看起来递归分支多(有2^50个节点),但栈的深度只看最长的那条调用链——也就是从50降到1的那条,总共才50层。其他分支的调用在返回后就立刻释放了栈空间,根本不会累积下来。
64位OpenJDK Server VM的默认栈大小大概是1MB左右,每层栈帧也就几百字节,50层完全没问题,但3000层直接就爆栈了。
至于你猜的JVM隐藏优化,其实不是主要原因:
- 首先,阶乘的写法不算尾递归——因为最后一步是
n * 递归结果,不是直接返回递归调用,所以JVM没法把它优化成循环(尾递归优化的核心就是把递归转成循环,避免栈累积)。而斐波那契的递归也不是尾递归,所以不存在针对它的特殊优化。 - HotSpot VM确实有一些动态优化比如栈上替换,但50层的递归深度根本轮不到触发这些优化,就已经顺利执行完了。
你可以自己验证一下:把斐波那契的参数改成10000,你会发现它也会触发StackOverflowError——这就说明本质就是递归深度的问题,不是什么针对性优化在搞鬼。
内容的提问来源于stack exchange,提问作者Zelphir Kaltstahl
相关产品推荐
相关产品推荐

