我问了这个问题,以了解如何增加JVM中的运行时调用堆栈大小。我已经找到了答案,并且还获得了许多有用的答案和注释,这些注释和注释与Java如何处理需要大量运行时堆栈的情况有关。我在回答摘要中扩展了我的问题。
最初,我想增加JVM堆栈的大小,以便程序运行时无需安装StackOverflowError
。
public class TT {
public static long fact(int n) {
return n < 2 ? 1 : n * fact(n - 1);
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
相应的配置设置是java -Xss...
具有足够大值的命令行标志。对于TT
上面的程序,它可以与OpenJDK的JVM一起工作:
$ javac TT.java
$ java -Xss4m TT
答案之一还指出,这些-X...
标志与实现有关。我在用
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3)
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)
也可以只为一个线程指定一个大堆栈(请参阅答案之一)。推荐java -Xss...
这样做,以避免浪费不必要的内存。
我很好奇程序上面到底需要多少堆栈,所以我把它n
增加了:
- -Xss4m足以满足
fact(1 << 15)
- -Xss5m足以满足
fact(1 << 17)
- -Xss7m足以满足
fact(1 << 18)
- -Xss9m足以满足
fact(1 << 19)
- -Xss18m足以满足
fact(1 << 20)
- -Xss35m足以满足
fact(1 << 21)
- -Xss68m足以满足
fact(1 << 22)
- -Xss129m足以满足
fact(1 << 23)
- -Xss258m足以满足
fact(1 << 24)
- -Xss515m足以满足
fact(1 << 25)
从上面的数字看来,Java为上述功能使用的每个堆栈帧大约有16个字节,这是合理的。
上面包含的枚举可能足够,而不是足够,因为堆栈要求不是确定性的:使用相同的源文件多次运行它,并且-Xss...
有时成功一次,有时会产生a StackOverflowError
。例如,对于1 << 20,-Xss18m
在10个中有7 个就足够了,而且-Xss19m
也不总是足够,但是-Xss20m
就足够了(总共100个中有100个用完了)。垃圾回收,JIT介入或其他原因是否会导致这种不确定性行为?
在StackOverflowError
(可能还有其他例外情况)处打印的堆栈跟踪仅显示运行时堆栈的最新1024个元素。下面的答案演示了如何计算到达的确切深度(可能比1024大很多)。
许多回答的人指出,考虑相同算法的替代方法,减少堆栈消耗的实现是一种良好且安全的编码做法。通常,可以将一组递归函数转换为迭代函数(使用例如Stack
对象,该对象填充在堆中而不是运行时堆栈中)。对于此特定fact
功能,将其转换非常容易。我的迭代版本如下所示:
public class TTIterative {
public static long fact(int n) {
if (n < 2) return 1;
if (n > 65) return 0; // Enough powers of 2 in the product to make it (long)0.
long f = 2;
for (int i = 3; i <= n; ++i) {
f *= i;
}
return f;
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
仅供参考,如上面的迭代解决方案所示,该fact
函数无法计算65以上(实际上甚至20以上)的数字的确切阶乘,因为Java内置类型long
会溢出。进行重构,fact
以使其返回a BigInteger
而不是long
大型输入也能产生准确的结果。