面试问题:符合垃圾收集条件的对象

提供以下代码:

class A { Boolean b; A easyMethod(A a){ a = null; return a; } public static void main(String [] args){ A a1 = new A(); A a2 = new A(); A a3 = new A(); a3 = a1.easyMethod(a2); a1 = null; // Some other code } } 

问题是在// Some other code之前// Some other code多少对象符合垃圾收集条件// Some other code

然后正确的答案是(至少那是面试官的答案): 2 – 布尔值b因为它是一个包装器和a1

你能帮我解释一下为什么a2a3没有被垃圾收集?

后期编辑:

  • 好的,我想我现在明白了。 起初有点令人困惑,但现在我确信面试官是错的。 我最初的错误是,起初我并不认为Java只是通过值传递,因此不可能从以“a2”作为参数的函数内部生成a2 null,因为a2实际上是a2的副本。
  • 布尔b的部分确实非常明显。

谢谢你的回答,我会在那之后发一些面试反馈:)。

假设go应该是easyMethod它就像这样工作

 class A { Boolean b; A easyMethod(A a){ a = null; // the reference to a2 was passed in, but is set to null // a2 is not set to null - this copy of a reference is! return a; // null is returned } public static void main(String [] args){ A a1 = new A(); // 1 obj A a2 = new A(); // 2 obj A a3 = new A(); // 3 obj a3 = a1.go(a2); // a3 set to null and flagged for GC - see above for why a1 = null; // so far, a1 and a3 have been set to null and flagged // Some other code } } 

两个对象有资格进行垃圾回收(a1和a3)。 b不是因为它只是对null的引用。 从未制作过Boolean

为了解决// Some other code可能存在的愚蠢的细微差别,我将问题重写为以下内容:

判断并解释以下输出:

 class A { int i; A(int i) { this.i = i; } public String toString() { return ""+i; } A go(A a){ a = null; // the reference to a2 was passed in, but is set to null // a2 is not set to null - this copy of a reference is! return a; // null is returned } public static void main(String [] args){ A a1 = new A(1); // 1 obj A a2 = new A(2); // 2 obj A a3 = new A(3); // 3 obj a3 = a1.go(a2); // a3 set to null and flagged for GC - see above for why a1 = null; // so far, a1 and a3 have been set to null and flagged test(a1); test(a2); test(a3); } static void test(A a) { try { System.out.println(a); } catch(Exception e) { System.out.println((String)null); } } } 

并输出:

 c:\files\j>javac A.java c:\files\j>java A null 2 null 

后续的是,那时a1和a3符合GC的条件,而a2则没有。

这个问题的教训是“将对象引用传递给方法并将该引用设置为null不会导致原始引用为空”。 这是面试官试图测试的知识。

如果a1.go(a2)实际上是a1.easyMethod(a2) ,答案确实是2,但不是你列出的那个。 正如Bozho正确指出的那样, b没有初始化,所以它没有引用任何对象。 在注释点有资格进行垃圾收集的两个对象是最初由a1a3引用的对象。

a1显然是无效的,并且a3被重新分配给a1.easyMethod(a2)的返回值,该值为null。 但是, a2不受方法调用的影响,因为Java是按值传递的 ,因此只将引用a2的副本传递给方法。 即使副本设置为null,也不会影响原始a2的值。

对于a2的原始拒绝,它实际上完全取决于“其他一些代码”中发生的事情。 如果“某些其他代码”不使用a2或a3,则原始a2对象有资格进行垃圾回收。

那是因为运行时不必关心词法范围。 它只需要知道永远不能再引用一个对象。 因此,如果“某些其他代码”不使用a2或a3,则它们指向的对象永远不会再次引用 ,因此已经可用于垃圾回收。

首先,访谈者对布尔值是错误的 – 这个代码没有创建这样的对象,所以没有什么可以被垃圾收集。

ba2类的变量称为垃圾收集是不正确的。 对象是垃圾收集的,而不是变量。 如果范围内变量引用了一个对象,那么它就不能被垃圾收集。 简单地说,只有当一个对象不再被任何变量引用时,它才能被垃圾收集。

因此,我们在此代码中创建了三个A实例。 它们以a1等引用开始,但由于变量引用发生了变化,我将对象实例称为A1,A2和A3。 由于你没有显示go方法的定义,我将假设它是对easyMethod的调用。

由于变量a1被重新赋值为null,因此没有任何内容指向实例A1,因此可以对其进行垃圾回收。

由于变量a2永远不会被重新分配( easyMethod中的赋值不会影响原始变量),因此无法对实例A2进行垃圾回收。

由于easyMethod始终返回null并且a3被赋予该方法的结果,因此没有任何内容指向实例A3,因此它也可以被垃圾收集。

你能帮我解释一下为什么a2和a3没有被垃圾收集?

因为a2和a3不是对象。 它们是变量。 变量不可收集,原因很简单,因为它们不可分配。

问题是在// Some other code.之前// Some other code.多少对象符合垃圾收集条件// Some other code.

这个问题是荒谬的。

垃圾收集器在运行时根据那里可用的信息行事。 可达性由存储在寄存器,线程堆栈和全局变量中的全局根确定。 寄存器和堆栈的内容是完全破坏代码的许多编译阶段的结晶。 源代码中的词法范围和行号的概念不再存在,因此询问有关GC在源代码中的某些点可能看到的内容的问题是没有意义的,因为这些点在GC的世界中不存在。

因此,我们必须首先假设源代码与生成的代码之间存在直接对应关系,否则我们甚至无法理解问题所引用的代码中的要点。 我无法想到在实践中实际执行此操作的任何工作VM,事实上,Java可能具有高级语言结构,甚至无法以这种方式编译为字节码。

接下来,我们必须假设一个模型,用于将本地引用保存在堆栈中的方式。 为了得出随机答案而不是假设一些定义不明确的模型,我将表明模型的选择使我们得出的答案范围从“没有资格获得GC”到“一切都符合GC条件”。 这一系列合理的答案真正凸显了这个问题的严重性。

考虑一个简单的模型,其中中间值(所有子表达式的结果以及变量)被推入堆栈直到函数结束。 简单的编译器和Windows Phone 7上的HLVM和.NET等虚拟机实际上在实践中的工作方式与此类似,即使这样可以保留超过必要的时间。 使用此模型,每个new A()将引用推送到堆栈,直到函数返回,这样所有三个都可以在相关点从堆栈到达,因此,没有资格进行垃圾回收。

考虑一个模型,在该模型中,从第一个点开始从堆栈中删除引用,从而不再读取它们。 这更接近于像.NET和OCaml这样的生产虚拟机如何工作,除非它们在可能的情况下将寄存器中的本地引用保留在其中,否则会溢出到函数调用的堆栈帧中的预分配条目,否则会覆盖死区域以最小化堆栈帧的大小。 使用此模型,所有引用都在相关点处死亡,因此无法访问任何引用,因此,所有三个新分配的对象都有资格进行垃圾回收,以及args数组及其引用的所有字符串。

因此,我们不仅表明nothing答案everything合理的,但我们甚至引用了实现这些模型的实际工作虚拟机和垃圾收集器,因此我们的预测是可测试的。

如果我在面试中得到这个问题,我会通过解释其中的一些来测试面试官。 如果他们表达了对学习兴趣的反应很好,那么我仍然会对这份工作感兴趣。 如果他们通过试图捍卫他们不明确的假设和不可预测的预测而做出反应,那么我就不想与他们合作。

你的问题很混乱。

垃圾收集器处理对象,而不是变量。 因此,当你说a2符合GC条件时,它就没有任何意义。 您应该说a2在第N行引用的对象符合N + M行的GC条件。

在您的示例中,您只有3个对象被实例化。 它是第一个创建A和最后一个创建符合GC条件的实例。