堆栈消耗的可变性从何而来?

发布于 2024-09-18 13:56:35 字数 2081 浏览 12 评论 0原文

在运行 这个问题 中的测试代码并摆弄 JVM 的线程堆栈时大小,我发现结果不一定是可重复的:对于某些堆栈大小值,程序有时会抛出java.lang.StackOverflowError,但有时不会

我的问题是:“是什么导致堆栈空间消耗的变化?”

另外,可以将中断堆栈放在该程序的主线程上吗?对于其他 JVM 实现和/或操作系统,结果是否同样具有不确定性?

测试代码

public class PointlessRecursion {

    private static final long N = 1 << 15;

    private static long addOne(long n) {
        return (n < 2) ? n : 1 + (addOne(n - 1));
    }

    public static void main(String[] args) {
        try {
            long x = addOne(N);
            System.out.println(x);
            assert(x == N);
            System.exit(0);
        } catch (StackOverflowError e) {
            System.exit(1);
        } catch (Throwable t) {
            System.err.println(t.toString());
            System.exit(2);
        }
    }
}

愚蠢的 bash 脚本,用于针对每个堆栈大小设置多次运行测试程序

#! /bin/bash
s=2000
while [ $s -lt 4100 ] ; do
    i=0
    pass=0
    fail=0
    while [ $i -lt 10 ] ; do
        java -Xss${s}k -cp ~/bin/classes PointlessRecursion > /dev/null
        if [ $? -eq 0 ] ; then
            pass=$((pass+1))
        elif [ $? -eq 1 ] ; then
            fail=$((fail+1))
        fi
        i=$((i+1))
    done
    echo ss=$s pass=$pass fail=$fail
    s=$(($s+100))
done

结果

$ java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)
$ ~/bin/stack-test.sh
ss=2000 pass=0 fail=10
ss=2100 pass=1 fail=9
ss=2200 pass=0 fail=10
ss=2300 pass=2 fail=8
ss=2400 pass=1 fail=9
ss=2500 pass=1 fail=9
ss=2600 pass=2 fail=8
ss=2700 pass=6 fail=4
ss=2800 pass=3 fail=7
ss=2900 pass=1 fail=9
ss=3000 pass=3 fail=7
ss=3100 pass=3 fail=7
ss=3200 pass=6 fail=4
ss=3300 pass=2 fail=8
ss=3400 pass=4 fail=6
ss=3500 pass=10 fail=0
ss=3600 pass=9 fail=1
ss=3700 pass=10 fail=0
ss=3800 pass=10 fail=0
ss=3900 pass=10 fail=0
ss=4000 pass=10 fail=0

Whilst running test code from this question and fiddling with the JVM's thread stack size, I found that results were not necessarily repeatable: there were values of stack size for which the program would sometimes throw java.lang.StackOverflowError, but sometimes not.

My question is: "What is causing the variation in stack space consumption?"

Also, can an interrupt's stack be put on this program's main thread? Will results be similarly non-deterministic for other JVM implementations and/or operating systems?

Test Code

public class PointlessRecursion {

    private static final long N = 1 << 15;

    private static long addOne(long n) {
        return (n < 2) ? n : 1 + (addOne(n - 1));
    }

    public static void main(String[] args) {
        try {
            long x = addOne(N);
            System.out.println(x);
            assert(x == N);
            System.exit(0);
        } catch (StackOverflowError e) {
            System.exit(1);
        } catch (Throwable t) {
            System.err.println(t.toString());
            System.exit(2);
        }
    }
}

Silly bash script for running test program multiple times for each stack size setting

#! /bin/bash
s=2000
while [ $s -lt 4100 ] ; do
    i=0
    pass=0
    fail=0
    while [ $i -lt 10 ] ; do
        java -Xss${s}k -cp ~/bin/classes PointlessRecursion > /dev/null
        if [ $? -eq 0 ] ; then
            pass=$((pass+1))
        elif [ $? -eq 1 ] ; then
            fail=$((fail+1))
        fi
        i=$((i+1))
    done
    echo ss=$s pass=$pass fail=$fail
    s=$(($s+100))
done

Results

$ java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)
$ ~/bin/stack-test.sh
ss=2000 pass=0 fail=10
ss=2100 pass=1 fail=9
ss=2200 pass=0 fail=10
ss=2300 pass=2 fail=8
ss=2400 pass=1 fail=9
ss=2500 pass=1 fail=9
ss=2600 pass=2 fail=8
ss=2700 pass=6 fail=4
ss=2800 pass=3 fail=7
ss=2900 pass=1 fail=9
ss=3000 pass=3 fail=7
ss=3100 pass=3 fail=7
ss=3200 pass=6 fail=4
ss=3300 pass=2 fail=8
ss=3400 pass=4 fail=6
ss=3500 pass=10 fail=0
ss=3600 pass=9 fail=1
ss=3700 pass=10 fail=0
ss=3800 pass=10 fail=0
ss=3900 pass=10 fail=0
ss=4000 pass=10 fail=0

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

临风闻羌笛 2024-09-25 13:56:35

如果它与 HotSpot 编译器启动的时间有关,我不会感到惊讶,特别是如果您在多核系统上运行。

编辑:要检查这一点,您可以使用-Xint启动测试。如果您开始获得可重现的结果,则不确定性行为可能是由 HotSpot 编译器引起的。

I would not be surprised if it had something to do with the timing the HotSpot compiler kicks in, specially if you are running on a multicore system.

EDIT: To check this you can launch your test with -Xint. IF you start getting reproducible results, then the nondeterministic behavior is probably caused by the HotSpot compiler.

世俗缘 2024-09-25 13:56:35

我没有观察到这种行为。在我的环境(Windows 7 x64、1.6.0_19 x86 JDK)中,它们在某个点上始终失败,然后开始在 ss=1400 处传递(我将其降低,因为它们都通过您的参数传递)。

我唯一改变的是不指定类路径(我从与类文件相同的目录运行脚本)。但即使指定了类路径,我也得到了相同的结果。

您确定您捕获的实际上是 StackOverflowError 吗?我会仅捕获该错误,而不是通用的 Throwable,以确保它不是其他错误,例如 OutOfMemoryError。

//...
} catch (StackOverflowError e) {
    System.exit(1);
}

这样,如果它实际上不是您收到的 StackOverflowError,它将打印堆栈跟踪。

I don't observe this behaviour. In my environment (Windows 7 x64, 1.6.0_19 x86 JDK) they fail consistently up to a certain point and then start passing at ss=1400 (I bumped it down since they were all passing with your parameters).

The only thing I changed was to not specify a classpath (I ran the script from the same directory as the class file). But even when specifying a classpath I got the same results.

Are you sure it's actually StackOverflowError that you're catching? I would catch that error solely instead of the generic Throwable to make sure it isn't something else, like an OutOfMemoryError.

//...
} catch (StackOverflowError e) {
    System.exit(1);
}

That way it'll print a stacktrace if it's not actually a StackOverflowError you're getting.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文