我问这个问题去了解如何增加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可以足够用于事实(1 <<15)
> -Xss5m可以足够用于事实(1 << 17)
> -Xss7m对于事实可以是足够的(1 <<18)
> -Xss9m对于事实可以足够(1 <<19)
> -Xss18m可以足够用于事实(1 <<20)
> -Xss35m可以足够用于事实(1 << 21)
> -Xss68m可以足够用于事实(1 <<22)
> -Xss129m可以足够用于事实(1 << 23)
> -Xss258m可以足够用于事实(1 <<24)
> -Xss515m可以足够用于事实(1 <25)
从上面的数字,似乎Java使用大约16字节每堆栈帧的上述功能,这是合理的。
上面的枚举包含可以足够而不是足够,因为堆栈要求不是确定性的:使用相同的源文件运行它多次,并且相同的-Xss …有时成功,有时会产生StackOverflowError。例如。对于1 < 20,-Xss18m足够在10次中的7次运行,并且-Xss19m也不总是足够,但-Xss20m是足够的(100次中的所有100次运行)。垃圾收集,JIT插入,还是其他原因导致这种非确定性的行为? 在StackOverflowError(以及可能还有其他异常)处打印的堆栈跟踪仅显示运行时堆栈的最近的1024个元素。下面的答案演示了如何计算到达的精确深度(可能大于1024)。 许多响应的人已经指出,它是一个好的和安全的编码实践,考虑替代的,更少的堆栈饥饿的实现相同的算法。通常,可以将一组递归函数转换为迭代函数(使用例如Stack对象,该对象被填充在堆上而不是运行时栈上)。对于这个特定的事实函数,它是很容易转换它。我的迭代版本将如下所示:
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));
}
}
FYI,如上面的迭代解决方案所示,事实函数无法计算高于65(实际上,甚至高于20)的数字的确切阶乘,因为Java内置类型long将溢出。重构事实,所以它将返回一个BigInteger,而不是long将产生精确的结果大输入以及。