假设:
interface Animal { void makeNoise(); }
class Horse implements Animal {
Long weight = 1200L;
public void makeNoise() { System.out.println("whinny"); }
}
public class Icelandic extends Horse {
public void makeNoise() { System.out.println("vinny"); }
public static void main(String[] args) {
Icelandic i1 = new Icelandic();
Icelandic i2 = new Icelandic();
Icelandic i3 = new Icelandic();
i3 = i1; i1 = i2; i2 = null; i3 = i1; //<-- line 14
}
}
当到达第14行时,有多少对象符合垃圾收集器的条件?
一个。 0 B. 1 C. 2 D. 3 E. 4 F. 6
答案是E.为什么?
答案 0 :(得分:4)
这个问题,特别是坚持4的特定“正确”答案,没有多大意义,因为它引出了一些关于JVM如何工作的假设,这些假设在最好的情况下或者完全错误都是天真的。
它做错的第一件事就是假设知道有多少对象被创建。
main
方法创建了三个Icelandic
个实例,其中包含使用值weight
初始化的继承字段1200L
。值1200L
使用允许的方法Long.valueOf
通过自动装箱转换为Long
实例,但不需要缓存频繁请求的值。因此,我们无法预测此处创建的Long
个实例的数量,可能是一个或三个,但如果缓存不适用于所有调用,则无论出于何种原因,即使是两个也是可能的。
然后代码正在使用包含三个实例的局部变量,并且应该在第14行之后仅保留这些实例中的一个,但是在该行之后没有相关代码,此状态可能具有相关性。在该行之后发生的唯一事情是从main
方法返回,因此在第14行所有局部变量超出范围之后。
是否有一个执行点可以与“第14行之后”关联,但仍然在方法main
中,其中Icelandic
的一个实例在范围内取决于行调试信息是否具有已经包含在类文件中以及编译器如何将字节代码指令映射到这些行号。
如果编译器插入的return
字节代码指令与第15行没有关联,则{14}方法中创建的所有实例可能在第14行之后被垃圾收集,但请记住{{1} 1}}实例仍可能从全局缓存中引用,因此无法进行垃圾回收。我们根本无法预测。
JVM执行的另一个方面是:优化。如果JVM对程序语义没有任何影响,则允许它们执行“转义分析”和删除对象创建。在您的程序中,从JVM的角度来看,所有对象都是未使用的。所以另一个合法执行场景是这个main
方法永远不会创建任何对象,然后根本没有对象可以进行垃圾收集。
答案 1 :(得分:2)
因此每个对象都包含一个Long对象,因此当每个Icelandic对象被标记为垃圾收集时,它指向的Long
值也是如此。因此:
Icelandic i1 = new Icelandic();
Icelandic i2 = new Icelandic();
Icelandic i3 = new Icelandic();
i3 = i1; i1 = i2; i2 = null; i3 = i1;
原始i3对象丢失,原始i1值丢失。 i1和i3都指向原始i2保持活着(并且i2指向null,保持任何活着)。这使得2个对象被标记为垃圾收集(原始i1和原始i3)以及它们指向的Long
值,为您提供标记为垃圾收集的4个项目。
答案 2 :(得分:2)
我会选择符合条件的Icelandic 2对象(对象1和对象3)。 由于每个冰岛人拥有一个Long对象,它给我们4个。
答案 3 :(得分:1)
重要的事情就在这里
Icelandic i1 = new Icelandic();
Icelandic i2 = new Icelandic();
Icelandic i3 = new Icelandic();
i3 = i1; i1 = i2; i2 = null; i3 = i1;
所以i3
立即丢失了它的引用,以便原始对象可以收集。
i1
和i3
引用i2
因此i1s原始对象已准备好进行收集。
然后i2
丢失了它的引用,所以所有3都为null并且收集了垃圾。
我个人一起去3.这是一个非常引人深思的问题。
答案 4 :(得分:1)
在Line14之后,只有原始i2对象处于活动状态,没有引用指向原始i1和i3,因此它们有资格使用垃圾收集器,并且此对象具有基本类型,长字段,丢失它,所以2 * 2 = 4;