1

在下面的代码中,只有一个对象在运行时从堆内存中清除(通过调用System.gc())。为什么只有一个对象被回收?

 class A{
    A a;
    public A(A a){
      this.a=a;
    }
    public static void main(String args[]){
      A a1=null;
      A a2=new A(new A(null));
      A a3=new A(a2);
      a1=a3;
      a1.a=new A(null);
      a2.a=null;  //Line12
      System.gc();
    }
  }
4

3 回答 3

3

我认为尝试在 Java 中确定准确的 GC 收集时间是很可怕的,但无论如何..

.. Java 垃圾收集适用于对象可达性。如果一个对象是强可达的——也就是说,如果它可以从可达性或已知根的任何路径访问,包括局部变量——那么这个对象就不能被回收1

考虑这个分解,其中 W、X、Y、Z 代表 A 的不同实例。我用来显示任何特定 A 的语法是instance {a-> instance}wherea指代成员变量。请记住,每个都new创建不同的实例,并且对象值的分配不会创建新对象(因此,相同的 Y 对象- 现在由 a1 和 a3 共享 - 在a1.a=new A(null)分配期间被修改)。

  A a1=null;                // a1 = null
  A a2=new A(new A(null));  // a2 = W {a-> X}
  A a3=new A(a2);           // a3 = Y {a-> W}    
  a1=a3;                    // a1 = a3 = Y {a-> W}
  a1.a=new A(null);         // a1 = a3 = Y {a-> Z}
  a2.a=null;                // a2 = W {a-> null}
  System.gc();

  // Then:
  // a1 = a3 = Y {a-> Z}    (Expanding: Y {a-> Z {a-> null}})
  // a2 = W {a-> null}

所以最后变量 a1 和 a3 - 它们是可达性根- 是“引用” Y {a->Z},而 a2 是“引用” W {a->null}。这意味着 W、Y 和 Z 仍然是强可达的(Z 是通过 Y 强可达的)并且不被认为有资格在 Java-land 2中进行回收。

只有 X 在线路上不再可达System.gc()。然而,这并不意味着X 将被垃圾回收,即使有明确的 GC 调用 - 它只意味着 X 有资格在未来的某个时候被回收。

当然,一旦函数结束,所有局部变量都不再是可达根,并且没有一个 A 对象是强可达的,这使得它们都符合条件:)


1即使不使用传统的基于 Mark & Sweep 的方法(在 JVM 中常见),使用对象可达性来讨论 GC 仍然有效,因为相同的规则适用于任何正常运行的 GC 系统:如果有机会对象可以被访问过,无法回收。反过来说:如果一个对象不能被访问,那么它应该被回收。

2确定可达性根的规则因语言/运行时而异。例如,在某些边缘情况下,C#/CLR 处理存在细微差别。

于 2013-09-23T19:33:41.557 回答
3

正如其他人指出的那样,当一个对象既不能作为变量的值直接访问,也不能间接访问时,可以收集该对象,因为另一个可访问对象具有对它的引用。让我们一次浏览几行代码,看看随着时间的推移对象引用图的样子。

  A a1=null;               // 1
  A a2=new A(new A(null)); // 2
  A a3=new A(a2);          // 3
  a1=a3;                   // 4

第 1 行没有做太多,我们甚至可以消除它并将第 4 行更改为A a1 = a3没有任何不同的结果。第 2 行将a2的值设置为对 的新实例的引用A,称为 α,其a字段是对 的第二个新实例的引用A,称为 β。第 3 行创建 的一个新实例A,称为 γ,其a字段是对 α 的引用。因此,所有的 α、β 和 γ 都被引用。第 4 行使a1' 值成为对 γ 的引用。注意:

  • α 可以作为 直接访问a2,并且只要 γ 可以访问,就可以间接访问;
  • 只要 α 可以访问,β 就可以通过 α 间接访问;和
  • a1γ 可以作为和直接访问a3

第一记忆状态

下一个,

  a1.a=new A(null);        // 5

a1.a = new A(null)将γ 的a字段更新为 的新实例A,称为 δ。现在:

  • α 可以作为 直接访问a2,而不能通过其他任何东西间接访问;
  • β 只能通过 γ 间接访问;
  • γ 可直接访问为a1a3;和
  • δ 只能通过 α 间接访问。

第二记忆状态

最后,

  a2.a=null;               // 6

现在删除了对 β 的最后一个剩余引用。仍然有对 α、γ 和 δ 的引用。

  • α 可直接访问为a2;
  • β 根本无法直接或间接访问;
  • γ 可直接访问为a1a3;和
  • δ 只能通过 γ 间接访问。

最终记忆状态

因为 β 根本无法访问,所以它是垃圾收集的候选对象。javadocSystem.gc()说:

运行垃圾收集器。调用此方法表明 Java 虚拟机将努力回收未使用的对象,以使它们当前占用的内存可用于快速重用。当控制从方法调用返回时,虚拟机已尽最大努力回收所有丢弃的对象。

因此,当我们到达

  System.gc(); // 7

系统可以但不是必须收集 β。假设 β 将通过“尽最大努力回收所有丢弃的对象”来收集可能是安全的,但这不是保证。

于 2013-09-23T19:41:44.713 回答
0

检查以下评论:

public static void main(String args[]){
  A a1=null;
  A a2=new A(new A(null));  //a2.a = new A(null); //line 3
  A a3=new A(a2);           //a3.a = a2
  a1=a3;                    //a1 = a3
  a1.a=new A(null);         //a1.a = a3.a = null; a2 is still referenced
  a2.a=null;  //Line12      //a2.a = null => object at line 3 is cleared after gc
                            //because it is not referenced anymore by any variable.
  System.gc();
}
于 2013-09-23T19:34:22.973 回答