代码之家  ›  专栏  ›  技术社区  ›  geowa4

Java垃圾回收如何与循环引用一起工作?

  •  137
  • geowa4  · 技术社区  · 15 年前

    根据我的理解,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垃圾回收是如何处理的?(或者仅仅是内存消耗?)

    8 回复  |  直到 7 年前
        1
  •  136
  •   Holger    7 年前

    Java的GC认为对象是“垃圾”,如果它们不能通过从垃圾收集根开始的链来访问,那么这些对象将被收集。即使对象可以相互指向以形成一个循环,但如果从根目录中切断它们,它们仍然是垃圾。

    请参阅附录A中关于无法访问的对象的部分:中有关垃圾收集的真相 Java Platform Performance: Strategies and Tactics 为了血淋淋的细节。

        2
  •  114
  •   Aniket Thakur    10 年前

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

    How?
    

    有一些称为垃圾收集根(gc根)的特殊对象。它们总是可以到达的,任何根上有它们的对象也是可以到达的。

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

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

    enter image description here

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

    1. 算法遍历所有对象引用,从gc开始 根,并标记每一个被发现的有生命的物体。
    2. 所有未被标记对象占用的堆内存是 回收的它被简单地标记为自由,基本上是从 未使用的对象。

    因此,如果无法从gc根访问任何对象(即使它是自引用或循环引用的),它将受到垃圾收集的影响。

    当然,有时这可能会导致内存泄漏,如果程序员忘记去引用一个对象。

    enter image description here

    来源: Java Memory Management

        3
  •  11
  •   Jerry Coffin    15 年前

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

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

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

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

        4
  •  9
  •   Jörg W Mittag    15 年前

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

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

    这个简单的策略有一个你所描述的问题:如果a引用b和b引用a,那么它们的引用计数都可以 从未 小于1,这意味着它们永远不会被收集。

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

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

    顺便说一下, 其他 实现垃圾收集器的主要方法是 示踪 . 跟踪收集器基于 可达性 . 你从一些开始 根集 你知道的是 总是 可访问(例如,全局常量,或 Object 类、当前词法作用域、当前堆栈帧)然后 追踪 所有可从根集访问的对象,然后所有可从根集访问的对象访问的对象,依此类推,直到有传递闭包为止。一切都是 在那个闭包里是垃圾。

    由于循环只能在自身内访问,而不能从根集访问,因此将收集它。

        5
  •  5
  •   Sbodd    15 年前

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

    • 静态变量
    • 当前运行线程堆栈中的局部变量(包括所有适用的“this”引用)

    因此,在您的例子中,一旦局部变量a、b和c在方法结束时超出范围,就没有更多的gc根直接或间接地包含对三个节点中任何一个的引用,它们将有资格进行垃圾收集。

    如果你想的话,toubeer的链接有更多的细节。

        6
  •  4
  •   TofuBeer    7 年前

    This article (不再可用)深入讨论垃圾收集器(概念上…有几种实现方式)。你文章的相关部分是“A.3.4不可及”:

    A.3.4不可到达-当不再有对象时,对象进入不可到达状态 存在对它的强烈引用。当一个对象不可访问时,它是 收藏候选。注意下面的措辞:仅仅因为一个对象是 一个收藏品的候选人并不意味着它会马上 收集。jvm可以自由延迟收集,直到 对象正在使用的内存的即时需要。

        7
  •  0
  •   Amnon    15 年前

    垃圾收集通常并不意味着“如果没有其他东西指向某个对象,则清除该对象”(即引用计数)。垃圾收集大致意味着查找无法从程序访问的对象。

    因此在您的示例中,在a、b和c超出范围之后,它们可以被gc收集,因为您不能再访问这些对象了。

        8
  •  0
  •   Claudiu    15 年前

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