pts*_*pts 114 java stack-overflow stack
我问这个问题是为了了解如何在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));
}
}
Run Code Online (Sandbox Code Playgroud)
相应的配置设置是java -Xss...具有足够大值的命令行标志.对于TT上面的程序,它与OpenJDK的JVM一样:
$ javac TT.java
$ java -Xss4m TT
Run Code Online (Sandbox Code Playgroud)
其中一个答案还指出,这些-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)
Run Code Online (Sandbox Code Playgroud)
也可以仅为一个线程指定一个大堆栈(参见其中一个答案如何).建议使用此方法java -Xss...以避免为不需要它的线程浪费内存.
我很好奇上面的程序需要多大的堆栈,所以我运行它n增加了:
fact(1 << 15)fact(1 << 17)fact(1 << 18)fact(1 << 19)fact(1 << 20)fact(1 << 21)fact(1 << 22)fact(1 << 23)fact(1 << 24)fact(1 << 25)从上面的数字来看,似乎Java每个堆栈帧使用大约16个字节用于上述函数,这是合理的.
上面的枚举可以是足够的,而不是足够的,因为堆栈需求不是确定性的:使用相同的源文件多次运行它,并且-Xss...有时成功,有时会产生一个StackOverflowError.例如,对于1 << 20,-Xss18m在10次中有7次运行就足够了,并且-Xss19m总是不够,但-Xss20m足够了(在所有100次100次中).垃圾收集,JIT踢入或其他什么导致这种不确定行为?
打印在a处的堆栈跟踪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));
}
}
Run Code Online (Sandbox Code Playgroud)
仅供参考,正如上面的迭代解决方案所示,该fact函数无法计算65以上(实际上甚至超过20)的数字的精确因子,因为Java内置类型long会溢出.重构fact所以它将返回a BigInteger而不是long会产生大输入的精确结果.
Jon*_*eet 72
嗯......它适用于我,远远低于999MB的堆栈:
> java -Xss4m Test
0
Run Code Online (Sandbox Code Playgroud)
(Windows JDK 7,构建17.0-b05客户端VM和Linux JDK 6 - 与您发布的版本信息相同)
小智 11
我假设您通过堆栈跟踪中的重复行计算了"1024的深度"?
显然,Throwable中的堆栈跟踪数组长度似乎限制为1024.请尝试以下程序:
public class Test {
public static void main(String[] args) {
try {
System.out.println(fact(1 << 15));
}
catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was " +
e.getStackTrace().length);
}
}
private static int level = 0;
public static long fact(int n) {
level++;
return n < 2 ? n : n * fact(n - 1);
}
}
Run Code Online (Sandbox Code Playgroud)
如果要使用线程堆栈大小,则需要查看Hotspot JVM上的-Xss选项.在非Hotspot VM上可能会有所不同,因为JVM的-X参数是特定于分发的,IIRC.
在Hotspot上,这看起来像java -Xss16M你想要大小16兆.
java -X -help如果要查看可以传入的所有分发特定JVM参数,请键入.我不确定它是否在其他JVM上工作相同,但它会打印所有Hotspot特定参数.
对于它的价值 - 我建议限制你在Java中使用递归方法.它在优化它们方面不是太好 - 因为JVM不支持尾递归(请参阅JVM是否阻止尾调用优化?).尝试重构上面的析因代码以使用while循环而不是递归方法调用.
控制进程内堆栈大小的唯一方法是启动一个新方法Thread.但您也可以通过使用-Xss参数创建自调用子Java进程来进行控制.
public class TT {
private static int level = 0;
public static long fact(int n) {
level++;
return n < 2 ? n : n * fact(n - 1);
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(null, null, "TT", 1000000) {
@Override
public void run() {
try {
level = 0;
System.out.println(fact(1 << 15));
} catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was "
+ e.getStackTrace().length);
}
}
};
t.start();
t.join();
try {
level = 0;
System.out.println(fact(1 << 15));
} catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was "
+ e.getStackTrace().length);
}
}
}
Run Code Online (Sandbox Code Playgroud)