Java 中的垃圾回收器 - 将对象设置为空

2022-09-02 05:36:53

让我们假设,有一个树对象,有一个根树节点对象,每个树节点都有leftNode和rightNode对象(例如BinaryTree对象)

如果我打电话:

myTree = null;

树内相关的 TreeNode 对象到底发生了什么?也会被垃圾回收,或者我必须将树对象内的所有相关对象设置为空??


答案 1

Java中的垃圾回收是在“可访问性”的基础上执行的。JLS对该术语的定义如下:

“可访问对象是可以从任何实时线程在任何潜在的持续计算中访问的任何对象。

只要对象可访问1,它就不符合垃圾回收的条件。

JLS将其留给Java实现来弄清楚如何确定对象是否可以访问。如果实现不能确定,则可以自由地将理论上无法访问的对象视为可访问的对象......而不是收集它。(事实上,JLS允许实现不收集任何东西,永远!没有实际的实现可以做到这一点,尽管2.)

在实践中,(保守的)可达性是通过追踪来计算的。查看通过遵循从类(静态)变量和线程堆栈上的局部变量开始的引用可以达到的内容。


以下是这对您的问题意味着什么:

如果我调用:树内相关的 TreeNode 对象到底发生了什么?也会被垃圾回收,或者我必须将树对象内的所有相关对象设置为空??myTree = null;

假设 包含对树根的最后一个剩余可访问引用。myTree

  1. 不会立即发生任何事情。
  2. 如果内部节点以前只能通过根节点访问,则它们现在无法访问,并且符合垃圾回收的条件。(在这种情况下,不需要指定对内部节点的引用。null
  3. 但是,如果内部节点可以通过其他路径访问,则它们可能仍然可以访问,因此不符合垃圾回收的条件。(在这种情况下,分配给对内部节点的引用是一个错误。您正在拆除一个数据结构,其他人以后可能会尝试使用该结构。null

如果 不包含对树根的最后一个剩余可访问引用,则将内部引用清空是错误的,原因与 3 中的原因相同。以上。myTree


那么什么时候应该帮助垃圾回收器呢?null

您需要担心的情况是,您可以确定某些单元格(本地,实例或类变量或数组元素)中的引用不会再次使用,但编译器和运行时不能!这些案件大致分为三类:

  1. 类变量中的对象引用...(根据定义)永远不会超出范围。
  2. 仍在作用域中的局部变量中的对象引用...但不会被使用。例如:

     public List<Pig> pigSquadron(boolean pigsMightFly) {
       List<Pig> airbornePigs = new ArrayList<Pig>();
       while (...) {
         Pig piggy = new Pig();
         ...
         if (pigsMightFly) {
           airbornePigs.add(piggy);
         }
         ...
       }
       return airbornePigs.size() > 0 ? airbornePigs : null;
     }
    

    在上面,我们知道如果为 false,则不会使用 list 对象。但是没有主流的Java编译器可以解决这个问题。pigsMightFly

  3. 实例变量或数组单元格中的对象引用,其中数据结构不变量意味着不会使用它们。@edalorzo的堆栈示例就是一个例子。

应该注意的是,编译器/运行时有时可以弄清楚范围内的变量实际上是死的。例如:

public void method(...) {
    Object o = ...
    Object p = ...
    while (...) {
        // Do things to 'o' and 'p'
    }
    // No further references to 'o'
    // Do lots more things to 'p'
}

一些Java编译器/运行时可能能够在循环结束后检测到不需要“o”,并将变量视为死变量。


1 - 事实上,我们在这里谈论的是很强的可访问性。当您考虑软引用、弱引用和幻像引用时,GC 可达性模型会更加复杂。但是,这些与OP的用例无关。

2 - 在Java 11中,有一个名为Epsilon GC的实验性GC,它明确不收集任何东西。


答案 2

它们将被垃圾回收,除非您对它们有其他引用(可能是手动的)。如果你只是对树的引用,那么是的,它们将被垃圾收集。