提供以下代码:
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
之前有多少对象符合垃圾收集的条件。
然后正确答案是(至少那是面试官的答案): 2 - 布尔b
因为它是一个包装器a1
。
您能否请我解释一下为什么a2
和a3
没有被垃圾收集?
稍后编辑:
感谢您的回答,之后我会发送一些面试反馈:)。
答案 0 :(得分:19)
假设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不会导致原始引用为空”。这是面试官试图测试的知识。
答案 1 :(得分:7)
如果a1.go(a2)
实际上是a1.easyMethod(a2)
,那么答案肯定是2,但不是您列出的答案。正如Bozho正确指出的那样,b
没有被初始化,所以它没有引用任何对象。在评论点有资格进行垃圾回收的两个对象是最初由a1
和a3
引用的对象。
a1
显然已被清除,a3
被重新分配给a1.easyMethod(a2)
的返回值,该值为null。但是,a2
不受方法调用的影响,因为Java is pass by value,因此只会将参考a2
的副本传递给方法。即使副本设置为null,也不会影响原始a2
的值。
答案 2 :(得分:7)
对于a2的原始拒绝,它实际上完全取决于“其他一些代码”中发生的事情。如果“其他一些代码”不使用a2或a3,那么原始的a2对象就有资格进行垃圾回收。
那是因为运行时不必关心 lexical 范围。它只需要知道永远不能再引用一个对象。因此,如果“其他一些代码”没有使用a2或a3,那么它们指向的对象永远不会再被引用,因此已经可用于垃圾收集。
答案 3 :(得分:5)
首先,访谈者对布尔值的看法是错误的 - 这个代码没有创建这样的对象,所以没有什么可以被垃圾收集。
将{strong>变量(如b
和a2
视为垃圾回收是不正确的。对象是垃圾收集的,而不是变量。如果范围内变量引用了对象,则无法对其进行垃圾回收。简单地说,只有当一个对象不再被任何变量引用时,它才能被垃圾收集。
因此我们在此代码中创建了三个A实例。它们从a1
等引用开始,但由于变量引用更改,我将对象实例称为A1,A2和A3。由于您没有显示go
方法的定义,我将假设它是对easyMethod
的调用。
由于变量a1被重新赋值为null,因此没有任何内容指向实例A1,因此可以对其进行垃圾回收。
由于变量a2永远不会被重新分配(easyMethod
中的赋值不会影响原始变量),因此不能对实例A2进行垃圾回收。
由于easyMethod
始终返回null
并且a3被分配了该方法的结果,因此没有任何内容指向实例A3,因此它也可以被垃圾收集。
答案 4 :(得分:5)
你可以请我解释一下为什么a2 和a3没有被垃圾收集 ?
因为a2和a3不是对象。它们是变量。变量不可收集,原因很简单,因为它们不可分配。
答案 5 :(得分:3)
问题是在
之前有多少对象符合垃圾回收的条件// Some other code.
问题是荒谬的。
垃圾收集器在运行时根据那里可用的信息行事。可达性由存储在寄存器,线程堆栈和全局变量中的全局根确定。寄存器和堆栈的内容是完全破坏代码的许多编译阶段的结晶。源代码中的词法范围和行号的概念不再存在,因此询问有关GC在源代码中的某些点可能看到的内容的问题是没有意义的,因为这些点在GC的世界中不存在。
因此,我们必须首先假设源代码与生成的代码之间存在直接对应关系,否则我们甚至无法理解问题所引用的代码中的要点。我无法想到在实践中实际执行此操作的任何工作VM,事实上,Java可能具有高级语言结构,甚至无法以这种方式编译为字节码。
接下来,我们必须假设一个模型,用于将本地引用保存在堆栈中的方式。为了得出一个随机答案而不是假设一些定义不明确的模型,我将表明模型的选择使我们得出的答案范围从“没有资格获得GC”到“一切都符合GC条件”。这一系列合理的答案确实突出了这个问题有多糟糕。
考虑一个简单的模型,其中中间值(所有子表达式的结果以及变量)被推入堆栈直到函数结束。简单的编译器和Windows Phone 7上的HLVM和.NET等虚拟机实际上在实践中的工作方式与此类似,即使这样可以保留超过必要的时间。使用此模型,每个new A()
将引用推送到堆栈,直到函数返回,这样所有三个都可以在相关点从堆栈到达,因此,没有资格进行垃圾回收。
考虑一个模型,在这个模型中,从第一个点开始不再从中读取引用。这更接近于像.NET和OCaml这样的生产虚拟机如何工作,除非它们在可能的情况下将本地引用保留在寄存器中,否则溢出到函数调用的堆栈帧中的预分配条目,否则会覆盖死区域以最小化堆栈帧的大小。使用此模型,所有引用都在相关点处死亡,因此无法访问任何引用,因此,所有三个新分配的对象都有资格进行垃圾回收以及args
数组及其引用的所有字符串
因此,我们不仅表明从nothing
到everything
的答案都是合理的,但我们甚至引用了实现这些模型的实际工作虚拟机和垃圾收集器,因此我们的预测是可测试的。 / p>
如果我在面试中接受了这个问题,我会通过解释其中的一些来测试面试官。如果他们通过表达对学习的兴趣做出反应,那么我仍然会对这份工作感兴趣。如果他们通过试图捍卫他们不明确的假设和不可预测的预测而做出反应,那么我就不想与他们合作。
答案 6 :(得分:2)
你的问题很混乱。
垃圾收集器处理对象,而不是变量。 因此,当你说a2符合GC条件时,它就没有任何意义。 您应该说,第N行a2引用的对象符合N + M行的GC条件。
在您的示例中,您只有3个对象被实例化。它是第一个创建A和最后一个创建符合GC条件的实例。