50

给定一个以复杂、循环的方式相互引用的类实例的聚合:垃圾收集器是否可能无法释放这些对象?

我隐约记得这是过去 JVM 中的一个问题,但我认为这在几年前就已经解决了。然而,对 jhat 的一些调查显示,循环引用是我现在面临的内存泄漏的原因。

注意:我一直认为 JVM 能够解析循环引用并从内存中释放这些“垃圾岛”。但是,我提出这个问题只是为了看看是否有人发现了任何例外。

4

10 回答 10

42

只有非常幼稚的实现才会有循环引用的问题。维基百科有一篇关于不同 GC 算法的好文章。如果您真的想了解更多信息,请尝试(亚马逊)垃圾收集:自动动态内存管理算法。Java 从 1.2 开始就有一个很好的垃圾收集器,在 1.5 和 Java 6 中有一个非常好的垃圾收集器。

改进 GC 的难点在于减少暂停和开销,而不是像循环引用这样的基本内容。

于 2008-10-07T00:11:53.620 回答
23

垃圾收集器知道根对象在哪里:静态变量、堆栈上的局部变量等,如果无法从根访问对象,那么它们将被回收。如果他们可以到达,那么他们需要留下来。

于 2008-10-07T00:02:47.320 回答
12

Ryan,根据您对Java 中的循环引用的评论判断,您陷入了从类中引用对象的陷阱,该类可能是由引导程序/系统类加载器加载的。每个类都由加载该类的类加载器引用,因此只有当类加载器不再可访问时才能进行垃圾收集。问题是引导程序/系统类加载器永远不会被垃圾收集,因此,系统类加载器加载的类中可访问的对象也不能被垃圾收集。

JLS 中解释了这种行为的原因。例如,第三版 12.7 http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.7

于 2008-10-07T18:31:42.670 回答
4

如果我没记错的话,那么根据规范,只能保证 JVM 不能收集什么(任何可达的),而不是它会收集什么。

除非您使用实时 JVM,否则大多数现代垃圾收集器应该能够处理复杂的引用结构并识别可以安全消除的“子图”。随着越来越多的研究想法进入标准(而不是研究)VM,效率、延迟和这样做的可能性会随着时间的推移而提高。

于 2008-10-07T00:35:31.983 回答
3

不,至少使用 Sun 的官方 JVM,垃圾收集器将能够检测到这些循环并在不再有来自外部的任何引用时释放内存。

于 2008-10-07T00:02:55.013 回答
3

Java 规范说垃圾收集器可以垃圾收集你的对象只有当它不能从任何线程访问时。

Reachable 意味着有一个引用,或从 A 到 B 的引用链,并且可以通过 C、D、...Z 进行所有它关心的事情。

自 2000 年以来,JVM 不收集东西对我来说一直不是问题,但你的里程可能会有所不同。

提示:Java 序列化缓存对象以提高对象网格传输效率。如果你有很多大的、瞬态的对象,并且你的所有内存都被占用了,请重置你的序列化程序以清除它的缓存。

于 2008-10-07T00:27:24.683 回答
3

当一个对象引用另一个对象,而另一个对象引用第一个对象时,就会发生循环引用。例如:

class A {
private B b;

public void setB(B b) {
    this.b = b;
}
}

class B {
private A a;

public void setA(A a) {
    this.a = a;
}
}

public class Main {
public static void main(String[] args) {
    A one = new A();
    B two = new B();

    // Make the objects refer to each other (creates a circular reference)
    one.setB(two);
    two.setA(one);

    // Throw away the references from the main method; the two objects are
    // still referring to each other
    one = null;
    two = null;
}
}

如果有循环引用,Java 的垃圾收集器足够聪明,可以清理对象,但是不再有任何活动线程对对象有任何引用。所以像这样的循环引用不会造成内存泄漏。

于 2013-04-01T17:25:18.993 回答
2

只是为了放大已经说过的话:

我已经工作了六年的应用程序最近从 Java 1.4 更改为 Java 1.6,我们发现我们必须添加静态引用到我们之前甚至没有意识到可以进行垃圾收集的东西。我们以前不需要静态引用,因为垃圾收集器过去很糟糕,现在好多了。

于 2008-10-07T00:10:54.373 回答
2

引用计数 GC 因这个问题而臭名昭著。值得注意的是,Suns JVM 不使用引用计数 GC。

如果无法从堆的根目录访问对象(通常,至少通过类加载器,如果没有其他情况0,那么对象将被销毁,因为它们在典型的 Java GC 期间没有被复制到新堆。

于 2008-10-07T00:20:52.277 回答
0

垃圾收集器是一个非常复杂的软件——它已经在一个巨大的 JCK 测试套件中进行了测试。这不是完美的,但很有可能只要 java 编译器(javac)会编译你的所有类并且 JVM 会实例化它,那么你应该很好。

再说一次,如果您持有对该对象图根的引用,则不会释放内存,但是如果您知道自己在做什么,则应该没问题。

于 2008-10-07T00:02:54.903 回答