3. class Beta{ }
4. class Alpha {
5. static Beta b1;
6. Beta b2;
7. }
8. public class Tester {
9. public static void main(String[] args) {
10. Beta b1 = new Beta(); Beta b2 = new Beta();
11. Alpha a1 = new Alpha(); Alpha a2 = new Alpha();
12. a1.b1 = b1;
13. a1.b2 = b1;
14. a2.b2 = b2;
15. a1 = null; b1 = null; b2 = null;
16. // do stuff
17. }
18. }
我认为2个对象将可用于垃圾收集
答案取决于Java实现。。。以及你选择如何解释这个问题。特别是第16行到底是什么意思?
- 如果我们将第16行解释为Java编译器所解释的,那么它只是一个注释。它什么都不做
- 另一种解释是第16行是编辑性的";"短手";对于某些真实Java代码,这些代码可能使用或不使用
a2
中的值,也可能不使用(静态)变量Alpha.b1
中的值
根据Java 6的JLS(12.6.1):
"可达对象是指可以在任何潜在的连续计算中从任何活动线程访问的任何对象。程序的优化转换可以设计为将可访问的对象数量减少到少于那些天真地被认为是可访问的。例如,编译器或代码生成器可以选择将不再使用的变量或参数设置为null,以使此类对象的存储可以更快地回收">
那么我们如何在这里应用它呢?
如果我们只将第16行视为一个注释,那么在main
方法中,此时a2
的值将不再影响任何潜在的连续计算。但是编译器/优化器/运行时可能无法推断出这一点。换句话说,a2
在第#16行的实际可达性状态是不清楚的。
同样,除非应用程序中有超出我们所展示内容的其他代码,否则Alpha.b1
的值也不会影响任何潜在的连续计算。事实上,由于该变量只被写入,因此它可以完全优化!但再一次,我们不知道编译器/优化器/运行时是否能够或将推断出Alpha.b1
是不可访问的,尽管(IMO)他们不太可能这样做。
还有几个额外的因素:
- 如果正在调试程序,这将影响优化器处理代码的方式,并可能影响可达性。例如,在
main
方法中设置断点可能会禁止a2
的任何幕后无效,因为程序员可能想要检查变量 - 这可能不明显,但可达性确定也取决于
main
方法是否已优化。对于使用HotSpot JIT编译器技术的当前一代JVM,它很可能不会在第一次(也是唯一一次)调用中进行优化。但是使用AOT编译器,它可能已经被优化了
然后有第16行的替代解释,作为一些实际代码的占位符。在这种情况下,就Alpha.b1
和a2
而言,所有赌注都是无效的。我们根本无法回答这个问题,除非我们知道";"东西";正在那里进行。
总之,对于有多少对象仍然可以访问,不可能给出一个明确正确的答案。
注意,当代码到达第16行时(它只是一个注释),可达性无论如何都是没有意义的。我想不出有什么会导致JVM在这一点上需要GC。。。或者在JVM进程退出之前的任何时间。(没有关机挂钩,不再支持"退出时完成"。)
让我们为对象分配明确的名称,这样我们就不会与变量和字段名称混淆。
- 第一个分配的Beta-betaOne
- 第二个分配的Beta-betaTwo
- 第一个分配的Alpha-alphaOne
- 第二个分配了Alpha-alphaTwo
在第12行中,我们将betaOne分配给一个静态变量。这意味着,除非我们清除该静态变量,否则betaOne将不符合垃圾收集的条件。
在第14行中,我们将betaTwo分配给alphaTwo中的一个非静态变量。然而,当我们到达第16行时,我们没有清除alphaTwo,所以它分配的变量仍然是可访问的,所以betaTwo也是可访问的。
所以betaOne是可达的,alphaTwo和betaTwo是可达的。这让我们无法访问alphaOne。
所以答案是:1。
如果对象不可访问,则可以进行垃圾收集。JLS,§12.6.1定义了不可达性:
每个对象都可以由两个属性来表征:它可以是可访问的、终结器可访问的或不可访问的,也可以是未终结的、可终结的或已终结的。
可达对象是指可以在任何潜在的持续计算中从任何活动线程访问的任何对象。
终结器可访问对象可以通过引用链从某个可终结对象访问,但不能从任何活动线程访问。
任何一种方法都无法访问无法访问的对象。
查看手头的程序,我们看到我们最初创建了四个对象:a1
、a2
、b1
和b2
(第10行和第11行);
我们设置了a1.b1 = b1;
(第12行)。这是一句欺骗不知情者的话:我们通过实例引用设置静态字段Alpha.a1
。
然后,我们设置a1.b2 = b1;
(第13行)。请注意,我们现在设置了a1
的实例字段b2
。
在第14行,我们设置了a2.b2 = b2
。再次注意,我们设置了a2
的实例字段b2
。
最后,我们null
除了a2
之外的所有参考文献。
对于以下分析,我将假设(urbandictionary.com
)第16行上的注释是访问所有活动变量的计算的占位符(如果我们不做这一假设,那么根据JLS,§12.6.1,未使用变量引用的对象是不可访问的)
现在分析可达性:
a2
仍然可以访问,因此此对象不符合垃圾收集的条件- 通过
a2
,我们仍然可以访问a2.b2
,因此以前由局部变量b2
引用的对象b是不合格的 - 通过类
Alpha
,我们仍然可以访问以前由b1
(Alpha.b1
)引用的对象,因此该实例也不合格 - 唯一不再可访问的实例是以前由
a1
引用的对象
因此,最终的答案是:一个对象有资格进行垃圾收集。
我们甚至可以通过PhantomReference
s:观察到1这种行为
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.TimeUnit;
class Ideone {
public static void main(String[] args) throws InterruptedException {
Beta b1 = new Beta();
Beta b2 = new Beta();
Alpha a1 = new Alpha();
Alpha a2 = new Alpha();
a1.b1 = b1;
a1.b2 = b1;
a2.b2 = b2;
PhantomReference<Alpha> a1Phantom =
new PhantomReference<>(a1, new ReferenceQueue<>());
PhantomReference<Alpha> a2Phantom =
new PhantomReference<>(a2, new ReferenceQueue<>());
PhantomReference<Beta> b1Phantom =
new PhantomReference<>(b1, new ReferenceQueue<>());
PhantomReference<Beta> b2Phantom =
new PhantomReference<>(b2, new ReferenceQueue<>());
a1 = null;
b1 = null;
b2 = null;
System.gc();
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
System.out.printf(
"a1 has %sbeen collected%n",
a1Phantom.isEnqueued() ? "" : "not ");
System.out.printf(
"a2 has %sbeen collected%n",
a2Phantom.isEnqueued() ? "" : "not ");
System.out.printf(
"b1 has %sbeen collected%n",
b1Phantom.isEnqueued() ? "" : "not ");
System.out.printf(
"b2 has %sbeen collected%n",
b2Phantom.isEnqueued() ? "" : "not ");
}
}
class Beta {
}
class Alpha {
static Beta b1;
Beta b2;
}
Ideone.com
演示
打印:
a1 has been collected
a2 has not been collected
b1 has not been collected
b2 has not been collected
表明仅收集到CCD_ 38。
上面代码的注释:
在Java 16+中,我们将使用a1.refersTo(null)
而不是a1.isEnqueued()
,因为方法isEnqueued()
已被弃用,并且方法refersTo(T)
已被添加。
1:该程序依赖于垃圾收集器收集所有符合条件的对象,这是不保证的。垃圾收集器可能根本没有运行,或者可能没有收集所有符合条件的对象。这个演示只是为了说明行为,而不是作为决定性的证据。关于证据,请阅读分析