这会导致Java内存泄漏吗?

3

这是一个面试问题,但我不太确定正确答案。假设我们有一些类如下:

public class A {
    public Object link;
    public A() {
        link = null;
    }
}

接下来我们创建两个实例:

A a1 = new A();
A a2 = new A();
a1.link = a2;
a2.link = a1;

然后我们释放引用:
a1 = null;
a2 = null;

那么问题来了:JVM会如何使用垃圾回收机制呢?它会在运行时立即删除这两个实例,还是只是标记内存空间并将它们保留下来?如果我有100万个这样的实例形成一个循环,并且没有外部引用,那么清理会使GC线程挂起吗?


1
简短回答:不会。标准JVM并非引用计数,它仅检查对象可达性,因此循环永远不会成为问题。 - user166390
标准的Java标记和清除垃圾收集器对这些没有问题。 - Hot Licks
请参考这个相关问题 - https://dev59.com/cFTTa4cB1Zd3GeqPoQAw - maerics
好的,第二个问题实际上是在问:一次删除100万个实例的过程是否会有性能相关的问题? - NSF
3个回答

3

这些对象本身可以通过任意数量的链接(就像你提到的一百万个循环)相互引用。如果没有“路线”返回到线程,则无论它们连接到多少其他垃圾回收节点,这些对象都有资格进行垃圾回收。

现在这并不意味着它们会被回收,只是它们有资格。因此,如果您的垃圾回收器决定不处理它们,那么我想这可能被视为内存泄漏。您不能保证它们会消失。


2
循环引用可能会导致某些垃圾回收策略(例如引用计数)的简单实现存在内存泄漏问题。(这并不是说引用计数本身是简单的,而是指糟糕的实现可能会遇到这个问题。)
然而,对于实现GC的人来说,这个问题是众所周知的,并且可以避免。此外,大多数现代JVM使用分代垃圾收集器,通常不会遇到这样的问题。

实际上,分代收集器在一定程度上确实存在这个问题。但这被认为是性能的权衡。而非分代标记-清除收集器则不会遇到循环引用的问题。 - Hot Licks
注意:像Python这样的引用计数实现也可以处理这种情况。它们可以使用“循环检测”来处理这些情况。 - user166390

0
根据当前代码逻辑,a1具有指向a2的成员变量,而a2具有指向a1的成员变量。当您执行a1 = null时,a1变得可以被GC回收。a2也是如此。现在,当GC运行时,它会尝试查看从根开始可达的所有对象,即使这两个对象相互引用,它们也会从根开始成为不可达对象(孤立情况),因此它们会被垃圾回收而没有任何问题。

当a1引用被赋值为null时,a1不符合垃圾回收的条件,因为它仍然在a2中有一个引用,并且a2仍然与线程相关联。一旦a2为空引用,对象将继续相互引用,但由于没有线程可以访问它们,它们就符合垃圾回收的条件。因此,“当您将a1 = null a1赋值为null时,它变得符合GC的条件”这个说法是不正确的。 - corsiKa

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