Java 中的循环引用

Posted

技术标签:

【中文标题】Java 中的循环引用【英文标题】:Circular References in Java 【发布时间】:2010-09-15 15:50:07 【问题描述】:

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

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

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

【问题讨论】:

【参考方案1】:

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

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 的垃圾收集器足够聪明,可以清理对象,但是不再存在对对象有任何引用的活动线程。所以像这样的循环引用不会造成内存泄漏。

【讨论】:

【参考方案2】:

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

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

【讨论】:

实际上,我们认为我们已经弄清楚了,但不幸的是现在看起来我们没有它。但是,是的,这是我们应该探索的一种可能性。【参考方案3】:

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

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

【讨论】:

强调一点,假设无限堆且从不垃圾收集的 JVM 是完全兼容的(尽管效率低下 ;-)【参考方案4】:

Java 规范说垃圾收集器可以垃圾收集您的对象 仅当它无法从任何线程访问时。

Reachable 表示存在从 A 到 B 的引用或引用链, 并且可以通过 C,D,...Z 进行所有它关心的事情。

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

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

【讨论】:

【参考方案5】:

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

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

【讨论】:

引用计数是天真的实现的一个很好的例子。 其实,这是一个聪明的实现,也是一个相当简单的实现,虽然它有其局限性,但它是一种可行的算法,尤其是在内存受限的环境中。 我一直在阅读有关 MacOs 开发的信息,显然 ObjectiveC 支持基于引用计数的垃圾收集以及类似于 Java 的更复杂的方案。【参考方案6】:

只有非常幼稚的实现才会有循环引用的问题。***对不同的 GC 算法有很好的article。如果您真的想了解更多信息,请尝试(亚马逊)Garbage Collection: Algorithms for Automatic Dynamic Memory Management 。 Java 从 1.2 开始就有了一个很好的垃圾收集器,在 1.5 和 Java 6 中也有一个非常好的垃圾收集器。

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

【讨论】:

【参考方案7】:

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

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

【讨论】:

如果之前无法到达,你怎么知道它消失了?我想知道是否有某种类型的链接你没有想到会导致系统保留它。一个线程或一个监听器。 当您必须删除对它们的所有引用时,您如何知道这些对象是否正在被垃圾回收?它们是弱引用还是您通过 JNI 有链接到它们,或者它是某种活动对象,即使它仍然处于活动状态也可以收集? 问题是当我们去引用它时,它就不见了。我忘记了确切的细节,因为那是几个月前的事了,但我想我们是通过 RMI 方法调用它的。 事实证明,内存泄漏可以追溯到其实例存储在集合中的类,并且该类本身具有对相关类对象的引用(对象类型) - 没有,我没有为这个写代码:-)【参考方案8】:

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

【讨论】:

【参考方案9】:

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

再一次,如果你持有对这个对象图根的引用,内存不会被释放,但是如果你知道你在做什么,你应该没问题。

【讨论】:

【参考方案10】:

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

【讨论】:

以上是关于Java 中的循环引用的主要内容,如果未能解决你的问题,请参考以下文章

怎么取消循环引用警告

为啥iOS的Masonry中的self不会循环引用

如何防止智能指针循环引用问题

JVM对象A和B循环引用,最后会不会不被GC回收?-------关于Java的GC机制

Java 虚拟机原理垃圾回收算法 ( Java 虚拟机内存分区 | 垃圾回收机制 | 引用计数器算法 | 引用计数循环引用弊端 )

检测和修复 JavaScript 中的循环引用