Java垃圾收集如何处理循环引用?

Java垃圾收集如何处理循环引用?,java,garbage-collection,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 =

据我所知,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
a
b
c
应该进行垃圾收集,但它们都被其他对象引用


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

如果对象无法通过从垃圾收集根开始的链访问,Java的GC会将其视为“垃圾”,因此将收集这些对象。即使对象可能相互指向以形成一个循环,但如果从根上断开,它们仍然是垃圾


有关详细信息,请参见附录A中“无法访问的对象:中关于垃圾收集的真相”一节。

垃圾收集通常并不意味着“在没有其他对象“指向”该对象时清理某些对象”(这是引用计数)。垃圾收集大体上意味着查找程序无法访问的对象

因此,在您的示例中,在a、b和c超出范围后,它们可以由GC收集,因为您无法再访问这些对象。

(不再可用)深入介绍了垃圾收集器(概念上……有几种实现)。您的帖子的相关部分是“A.3.4无法访问”:

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


Java GCs实际上并不像您所描述的那样工作。更准确的说法是,它们从一组基本对象(通常称为“GC根”)开始,并将收集从根无法访问的任何对象。
GC根包括以下内容:

  • 静态变量
  • 当前在运行线程堆栈中的局部变量(包括所有适用的“this”引用)
因此,在您的情况下,一旦局部变量a、b和c在方法末尾超出范围,就不再有直接或间接包含对三个节点中任何一个的引用的GC根,它们将有资格进行垃圾收集


如果您愿意,TofuBeer的链接会提供更多详细信息。

比尔直接回答了您的问题。正如Amnon所说,您对垃圾收集的定义只是引用计数。我只想补充一点,即使是非常简单的算法,如mark和sweep以及copy collection,也可以轻松处理循环引用。所以,没有什么神奇的

垃圾收集器从总是被认为是“可到达”的一些“根”位置开始,例如CPU寄存器、堆栈和全局变量。它的工作原理是找到这些区域中的任何指针,并递归地找到它们指向的所有对象。一旦找到所有这些,其他一切都是垃圾

当然,有很多变化,主要是为了速度。例如,大多数现代垃圾收集器都是“分代的”,这意味着它们将对象分为几代,当对象变老时,垃圾收集器在试图确定该对象是否仍然有效的时间间隔内运行的时间越来越长--它只是开始假设如果对象已经存在很长时间,很有可能它会继续活得更长

尽管如此,基本思想仍然是一样的:它都是基于从一些它认为理所当然仍然可以使用的东西的根集合开始,然后追踪所有的指针以找到其他可以使用的东西


有趣的是:垃圾收集器的这一部分与封送对象(如远程过程调用)的代码之间的相似程度可能会让人们感到惊讶。在每种情况下,您都是从某个对象的根集合开始,并跟踪指针以查找所有其他引用的对象…

您是正确的。您描述的垃圾收集的具体形式称为“引用计数”。在最简单的情况下,它的工作方式(至少在概念上,引用计数的大多数现代实现实际上是完全不同的)如下所示:

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
  • 每当添加对对象的引用时(例如,将其分配给变量或字段、传递给方法等),其引用计数将增加1
  • 每当删除对对象的引用时(方法返回、变量超出范围、字段重新分配给其他对象或包含字段的对象本身被垃圾回收),引用计数将减少1
  • 一旦引用计数达到0,就不再有对该对象的引用,这意味着没有人可以再使用它,因此它是垃圾并且可以被收集
这个简单的策略正是你所描述的问题:如果A引用B和B引用A,那么它们的引用计数都不能小于1,这意味着它们永远不会被收集

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

  • 别理它。如果您有足够的内存,您的周期很小且不频繁,并且您的运行时很短,那么您可能不需要收集周期。想象一下shell脚本解释器:shell脚本通常只运行几秒钟,不会分配太多内存
  • 将引用计数垃圾收集器与另一个没有周期问题的垃圾收集器组合起来。CPython就是这样做的,例如:CPython中的主垃圾收集器是一个引用计数收集器,但有时会运行一个跟踪垃圾收集器来收集周期
  • 检测循环