Java垃圾收集如何与循环引用一起使用?

Ale*_*yMK 148 java garbage-collection

根据我的理解,如果没有其他东西"指向"该对象,Java中的垃圾收集会清除一些对象.

我的问题是,如果我们有这样的事情会发生什么:

class Node {
    public object value;
    public Node next;
    public Node(object o, Node n) { value = 0; next = n;}
}

//...some code
{
    Node a = new Node("a", null), 
         b = new Node("b", a), 
         c = new Node("c", b);
    a.next = c;
} //end of scope
//...other code
Run Code Online (Sandbox Code Playgroud)

a,bc应该是垃圾收集,但它们都被其他对象引用.

Java垃圾收集如何处理这个问题?(或者它只是一个内存消耗?)

Bil*_*ard 148

Java的GC认为对象是"垃圾",如果它们无法通过从垃圾收集根开始的链到达,那么这些对象将被收集.尽管物体可以指向彼此形成一个循环,但如果它们从根部切断,它们仍然是垃圾.

请参阅附录A中的无法访问对象部分:Java平台性能中关于垃圾收集的真相:血腥细节的策略和策略.

  • @tangens"你有参考吗?" 在讨论垃圾收集.最好.双关语.永远. (80认同)
  • 你有参考吗?很难测试它. (9认同)
  • "......聪明到可以识别......"听起来令人困惑.GC不必识别循环 - 它们只是无法访问,因此垃圾 (4认同)
  • 我添加了一个参考.您还可以覆盖对象的finalize()方法以找出它何时被收集(尽管这是我建议使用finalize()的唯一方法). (3认同)

Ani*_*kur 124

是Java垃圾收集器处理循环引用!

How?
Run Code Online (Sandbox Code Playgroud)

有一些称为垃圾收集根(GC根)的特殊对象.它们总是可以访问的,任何拥有它们的对象也是如此.

一个简单的Java应用程序具有以下GC根源:

  1. 主方法中的局部变量
  2. 主线程
  3. 主类的静态变量

在此输入图像描述

为了确定哪些对象不再使用,JVM间歇性地运行非常适合称为标记和扫描算法的对象.它的工作原理如下

  1. 该算法遍历所有对象引用,从GC根开始,并将每个发现的对象标记为活动.
  2. 将回收未被标记对象占用的所有堆内存.它被简单地标记为自由,基本上没有未使用的对象.

因此,如果无法从GC根目录访问任何对象(即使它是自引用或循环引用),它将进行垃圾收集.

当然,如果程序员忘记取消引用某个对象,有时会导致内存泄漏.

在此输入图像描述

来源:Java内存管理

  • 在最后一张图片中,有一个不可到达的对象,但它在可到达的对象部分. (12认同)
  • 完美的解释!谢谢!:) (3认同)

Jer*_*fin 11

垃圾收集器从一些始终被视为"可访问"的"根"位置开始,例如CPU寄存器,堆栈和全局变量.它的工作原理是找到这些区域中的任何指针,并递归地找到它们指向的所有内容.一旦发现了这一切,其他一切都是垃圾.

当然,有很多变化,主要是为了速度.例如,大多数现代垃圾收集器都是"世代"的,意味着它们将对象分成几代,随着对象变老,垃圾收集器在它试图弄清楚该对象是否仍然有效的时间之间变得越来越长. - 它只是开始假设如果它已经存在了很长时间,那么它很可能继续存活更长时间.

尽管如此,基本思想仍然是相同的:它都是基于从一些根本开始的东西开始,它仍然可以使用它,然后追逐所有指针以找到其他可能正在使用的东西.

有趣的是:人们常常会对垃圾收集器的这一部分与远程过程调用之类的对象的编组代码之间的相似程度感到惊讶.在每种情况下,你都是从一些根对象开始,并追逐指针来找到那些引用的所有其他对象......

  • Ulterior Reference Counting 是由 2007 年设计 Immix 的同一个人在 2003 年设计的,所以我猜后者可能取代了前者。URC 是专门设计的,所以它可以与其他策略相结合,实际上 URC 论文明确提到,URC 只是迈向结合了跟踪和引用计数优势的收集器的垫脚石。我猜 Immix 就是那个收藏家。无论如何,Recycler 是一个*纯粹的*引用计数收集器,它仍然可以检测和收集循环:http://WWW.Research.IBM.Com/people/d/dfb/recycler.html (2认同)

Jör*_*tag 10

你是对的.您描述的垃圾收集的具体形式称为" 引用计数 ".它的工作方式(从概念上讲,至少,大多数现代的引用计数实现实际上以不同的方式实现)在最简单的情况下,如下所示:

  • 每当添加对象的引用(例如,它被赋值给变量或字段,传递给方法等)时,其引用计数增加1
  • 每当删除对象的引用时(该方法返回,变量超出范围,该字段被重新分配给另一个对象或包含该字段的对象自己被垃圾收集),引用计数减少1
  • 一旦引用计数达到0,就没有对该对象的更多引用,这意味着没有人可以再使用它,因此它是垃圾并且可以被收集

这个简单的策略确实存在你所描述的问题:如果A引用B和B引用A,那么它们的引用计数都不能小于1,这意味着它们永远不会被收集.

有四种方法可以解决这个问题:

  1. 忽略它.如果你有足够的内存,你的周期很小而且很少,你的运行时间很短,也许你可以逃脱只是不收集周期.想想一个shell脚本解释器:shell脚本通常只运行几秒钟,并且不会分配太多内存.
  2. 将您的引用计数垃圾收集器与另一个没有循环问题的垃圾收集器相结合.CPython这样做,例如:CPython中的主要垃圾收集器是一个引用计数收集器,但有时会运行跟踪垃圾收集器来收集周期.
  3. 检测周期.不幸的是,在图中检测周期是相当昂贵的操作.特别是,它需要与跟踪收集器几乎相同的开销,因此您也可以使用其中之一.
  4. 不要以天真的方式实现算法你和我会这样做:自20世纪70年代以来,已经开发了多种非常有趣的算法,它们以一种巧妙的方式将循环检测和引用计数结合在一起,比这两种方法都要便宜得多单独或做跟踪收集器.

顺便说一句,实现垃圾收集器的另一种主要方式(我已经多次暗示过这一点)正在追踪.跟踪收集器基于可达性的概念.你可以从一些你知道总是可以访问的根集开始(例如全局常量,或类,当前词法范围,当前堆栈框架),然后从那里跟踪可从根集中访问的所有对象,然后从根集可以访问的对象可以访问的所有对象,依此类推,直到你有传递闭包.那个封闭中没有的东西就是垃圾.Object

由于循环只能在其自身内访问,但无法从根集中访问,因此将收集该循环.

  • 由于问题是 Java 特定的,我认为值得一提的是,Java 不使用引用计数,因此问题不存在。另外[链接到维基百科](http://en.wikipedia.org/wiki/Garbage_collection_(computer_science))作为“进一步阅读”也会有所帮助。否则很棒的概述! (2认同)

Tof*_*eer 6

这篇文章(不再可用)深入介绍了垃圾收集器(概念上......有几种实现)。与您的帖子相关的部分是“A.3.4 Unreachable”:

A.3.4 Unreachable 当一个对象不再存在强引用时,它就会进入一个不可到达的状态。当一个对象不可访问时,它是一个收集的候选对象。请注意措辞:仅仅因为对象是收集的候选对象并不意味着它会立即被收集。JVM 可以自由地延迟收集,直到立即需要对象消耗的内存。


Sbo*_*odd 6

Java GC实际上并不像您描述的那样运行.更准确地说,它们从一组基础对象开始,通常称为"GC根",并将收集从根无法到达的任何对象.
GC根包括以下内容:

  • 静态变量
  • 当前变量(包括所有适用的'this'引用)当前位于正在运行的线程的堆栈中

因此,在您的情况下,一旦局部变量a,b和c超出了方法末尾的范围,就不会有更多的GC根直接或间接地包含对任何三个节点的引用,并且他们将有资格进行垃圾收集.

如果你需要,TofuBeer的链接有更多细节.