在Java中,哪些对象被放在堆栈上,哪些被放在堆上?

5

对于 Java 函数中的语句:

Xxx xxx = new Xxx() {
    public Abc abc(final Writer out) {
        return new SomeFunction(out) {
            boolean isDone = false;
            public void start(final String name) {
                /* blah blah blah */
            }
        };
    }
};

哪些变量(包括函数)会被放到堆上,哪些会被放到栈上?

我提这个问题是因为JVM出现了分段错误:

kernel: java[14209]: segfault at 00002aab04685ff8 rip 00002aaab308e4d0 rsp 00002aab04685ff0 error 6

00002aab04685ff800002aab04685ff0非常接近,看起来堆栈增长得太快了。我试图调查代码的这一部分,并怀疑在多次调用此函数时是否是问题的原因。如果堆栈被堆上某些变量引用,那么它可能不会清除吗?

2个回答

7

关于一个特定对象是否放在堆上的问题有点复杂。

一般来说,在Java中,所有对象都分配在堆上,因为方法可能返回或存储指向该对象的指针。如果对象被放在堆栈上,那么下次堆栈帧被放置时,它将被覆盖。

然而,HotSpot JIT编译器会进行称为逃逸分析的操作。该分析通过查看方法的代码来找出对象是否“逃逸”了该方法的范围。如果对象逃逸,则编译器可以安全地将其分配到堆栈上。

维基百科提供有关Java中的逃逸分析的更多信息,还涉及多线程和锁定。


关于堆栈溢出:调用栈上的堆栈帧在方法完成后总是被移除。实际上,甚至没有必要明确地将其删除。下一个框架将覆盖先前存在的内容。
此外,尽管在其他语言(如C)中可能会通过将非常大的对象放在堆栈上来导致堆栈溢出,但我不认为这种情况会在Java中发生。我期望Sun(Oracle)的工程师足够聪明,不会使VM存储堆栈上的巨大对象。
因此,堆栈溢出的唯一可能性是有太多嵌套的方法调用。由于堆栈空间足以处理任何“正常”的方法调用嵌套,因此堆栈溢出通常表示代码中存在无限(或非常大的)递归。

2
有许多原因更愿意将对象放在堆栈中:首先,堆栈更有可能在缓存中,为局部变量提供缓存本地性。其次,堆上的对象越少,垃圾回收器就越不忙碌。第三,堆栈上的对象保证只能由单个线程访问,因此无需锁定它们。 - rolve
1
@Harold:首先,在Java中你不应该看到段错误。这可能是JVM的一个bug。所以,我不认为你的代码是问题的原因。或者换句话说:你的代码可能是问题的原因,但这不是你的错... - rolve
2
@rolve 很好的总结。我想补充一点,编译是在后台线程中完成的,因此其影响相对较小。不幸的是,逃逸分析非常复杂,并且很少(根据我的经验)能够消除HotSpot编译器中甚至微不足道的对象。我看到过可变参数数组被删除,但没有看到其他的。JRockit JVM应该更好,但我自己没有看到过。 - Peter Lawrey
3
好的,请将需要翻译的内容提供给我。 - Peter Lawrey
@rolve 我已经问了几位工程师为什么会这样,他们的回答是“很复杂”。我曾考虑过编写一个可以为您进行优化的工具,但最终决定这也有点复杂。 ;) - Peter Lawrey
显示剩余5条评论

0

nameisDoneoutABC和指向匿名函数someFunction的“指针”都将在堆栈上;其余部分存储在堆中。


2
如果能多写一点散文就更好了——原帖作者无法从这个答案中学到任何东西。 - Romain
如果该函数被多次调用,是否会发生堆栈溢出,因为其中一些被堆中的对象引用? - Harold Chan
@Harold,这是一个完全不同的问题。堆栈溢出仅与堆栈上的对象有关。你可以阅读这篇文章。 - UmNyobe
name 是一个字符串对象。writer 也是一个对象。你确定它们在堆栈上吗? - paxdiablo
@UmNyobe,正如blackcoffeerider所说,由于一些函数被放在堆栈上,运行多次会导致堆栈溢出问题吗? - Harold Chan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接