为了估计最大调用深度,递归方法可以用给定的内存量来实现,在堆栈溢出错误可能发生之前计算所用内存的(近似)公式是什么?
许多人回答“它取决于”,这是合理的,所以让我们通过使用一个微不足道但具体的例子来删除一些变量:
public static int sumOneToN(int n) {
return n < 2 ? 1 : n + sumOneToN(n - 1);
}
很容易证明,在我的Eclipse IDE中运行它会导致n
低于1000(对我来说非常低)。
这个调用深度限制是否可以在不执行的情况下进行估算?
编辑:我不禁认为Eclipse的固定最大调用深度为1000,因为我得到了998
,但是有一个用于main,一个用于初始调用该方法,总共1000
。这是一个“太圆”的数字恕我直言,是一个巧合。我会进一步调查。我只是Dux开销-Xss vm参数;它是最大堆栈大小,因此Eclipse运行者必须在某处设置-Xss1000
答案 0 :(得分:25)
这显然是JVM-也可能是特定于架构的。
我测量了以下内容:
static int i = 0;
public static void rec0() {
i++;
rec0();
}
public static void main(String[] args) {
...
try {
i = 0; rec0();
} catch (StackOverflowError e) {
System.out.println(i);
}
...
}
使用
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)
在x86上运行。
使用20MB的Java堆栈(-Xss20m
),摊销成本在每次调用16-17个字节左右波动。我看到的最低值是16.15字节/帧。因此,我得出结论,成本是16字节,其余是其他(固定)开销。
采用单个int
的函数具有基本相同的成本,16字节/帧。
有趣的是,一个需要10 ints
的函数需要32个字节/帧。我不确定为什么成本太低。
在代码进行JIT编译后,上述结果适用。在编译之前,每帧成本更多,更多更高。我还没有找到一种可靠的估算方法。 但是,这确实意味着您无法可靠地预测最大递归深度,直到您可以可靠地预测递归函数是否已经过JIT编译。
所有这些都经过测试,ulimit
堆栈大小为128K和8MB。两种情况下的结果相同。
答案 1 :(得分:10)
只有部分答案:来自JVM Spec 7, 2.5.2,堆栈帧可以在堆上分配,堆栈大小可以是动态的。我无法肯定地说,但似乎应该可以让你的堆栈大小仅限于你的堆大小:
因为除了推送和弹出帧之外,永远不会直接操作Java虚拟机堆栈,所以可以对堆进行堆分配。
和
此规范允许Java虚拟机堆栈 固定大小或根据需要动态扩展和收缩 计算。如果Java虚拟机堆栈具有固定大小, 可以选择每个Java虚拟机堆栈的大小 创建该堆栈时独立。
Java虚拟机实现可以提供程序员或 用户控制Java虚拟机堆栈的初始大小, 以及在动态扩展或收缩Java的情况下 虚拟机堆栈,控制最大和最小尺寸。
所以这取决于JVM的实现。
答案 2 :(得分:4)
Addig to NPEs回答:
最大堆栈深度似乎灵活。以下测试程序打印非常不同的数字:
public class StackDepthTest {
static int i = 0;
public static void main(String[] args) throws Throwable {
for(int i=0; i<10; ++i){
testInstance();
}
}
public static void testInstance() {
StackDepthTest sdt = new StackDepthTest();
try {
i=0;
sdt.instanceCall();
} catch(StackOverflowError e){}
System.out.println(i);
}
public void instanceCall(){
++i;
instanceCall();
}
}
输出结果为:
10825
10825
59538
59538
59538
59538
59538
59538
59538
59538
我使用了此JRE的默认值:
java version "1.7.0_09"
OpenJDK Runtime Environment (IcedTea7 2.3.3) (7u9-2.3.3-0ubuntu1~12.04.1)
OpenJDK 64-Bit Server VM (build 23.2-b09, mixed mode)
所以结论是:如果你有足够的脓(即两次以上),你会得到第二次机会; - )
答案 3 :(得分:3)
答案是,这一切都取决于。
首先,可以更改Java堆栈大小。
其次,给定方法的堆栈帧大小可以根据不同的变量而变化。您可以查看Call stack - Wikipedia的“调用堆栈帧大小”部分以获取更多详细信息。
答案 4 :(得分:2)
取决于您的系统架构(32位或64位地址),局部变量的数量和方法的参数。 如果tail-recursive没有开销,那么编译器会将其优化为循环。
你看,没有简单的答案。
答案 5 :(得分:2)
您可以采用经验之路并尝试使用代码和-Xss
设置。有关详细信息,请参阅此处:JVM option -Xss - What does it do exactly?