对象何时适合进行垃圾回收?

18
在下面的代码中,假设已经调用了amethod方法。在哪一行/时刻原始由myObject引用的对象可以进行垃圾回收?

在下面的代码中,假设已经调用了amethod方法。在哪一行/时刻原始由myObject引用的对象可以进行垃圾回收?

class Test {
  private Object classObject;

  public void amethod() {
    Object myObject = new Object();
    classObject = myObject;
    myObject = null;
  }
}

如果classObject或者amethod有public、protected、default或static的访问修饰符,那么它会影响对象何时可以被垃圾回收吗?如果会,它会如何受到影响?

  • 我的第一个想法是,在Test对象可以被垃圾回收时,该对象也可以被垃圾回收。
  • 但是反过来看,优化器可能知道classObject从未被读取,这种情况下classObject = myObject;会被优化掉,myObject = null;成为该对象可以被垃圾回收的点。

当你说“the Object”时,你是指Test类的实例,Object classObject还是Object myObject - Luiggi Mendoza
@LuiggiMendoza中的“Object”始终指的是最初由myObject引用的对象。 - Dorothy
7个回答

22

只有当所有引用它的对象被丢弃后,该对象才会成为垃圾收集的候选对象。Java对象是通过引用进行分配的,因此当你拥有了一个引用

   classObject = myObject;

你将另一个引用分配给了堆上的同一对象。因此,这行代码

   myObject = null;

只会消除一个引用。如果要使myObject成为垃圾回收的候选对象,您需要拥有

  classObject = null;

编译器不允许像问题描述中那样优化掉对ivar的赋值吗? - Kartick Vaddadi
基于各种因素,包括引用类型(弱引用 vs 软引用),在任何情况下依赖此优化都不是直接的(或安全的)@KartickVaddadi。 - kolossus
@VaddadiKartick:规范明确说明堆变量不允许进行这种优化。请参考此回答 - Holger
这个答案中提到的理论隐含地假设所有引用都是强引用。该答案没有考虑到其他类型的引用,如软引用和弱引用。然而就所提供的例子而言,该答案是正确的。 - Spyros K
这个答案并不完全正确。当从GC根到对象的引用路径不存在时,对象就可以进行垃圾回收了。这里的区别在于,如果你创建了一个循环引用链,一旦它不再可达,它仍然会被垃圾回收。尽管该链中的每个对象始终存在引用。 - JSchlather

6

来自书籍 OCA Java SE 7

当一个对象不再被访问时,就会被标记为可回收,这可能发生在对象超出其作用域的时候。当对象的引用变量被赋值为显式null值或被重新初始化时,也可能发生这种情况。


4
这其实是Java语言规范中非常明确的一点,具体可以参见§12.6.1实现finalization

程序中可以设计优化转换,使可达对象的数量少于那些本来会被认为是可达的。例如,Java编译器或代码生成器可以选择将不再使用的变量或参数设置为null,从而导致存储该对象的存储空间更早地可以被回收。

如果一个对象字段中的值被存储在寄存器中,则程序可以直接访问寄存器而无需再次访问对象。这意味着对象已经成为垃圾......

但是

… Note that this sort of optimization is only allowed if references are on the stack, not stored in the heap.

For example, consider the Finalizer Guardian pattern:

   class Foo {
       private final Object finalizerGuardian = new Object() {
           protected void finalize() throws Throwable {
               /* finalize outer Foo object */
           }
       }
   } 

The finalizer guardian forces super.finalize to be called if a subclass overrides finalize and does not explicitly call super.finalize.

If these optimizations are allowed for references that are stored on the heap, then a Java compiler can detect that the finalizerGuardian field is never read, null it out, collect the object immediately, and call the finalizer early. This runs counter to the intent: the programmer probably wanted to call the Foo finalizer when the Foo instance became unreachable. This sort of transformation is therefore not legal: the inner class object should be reachable for as long as the outer class object is reachable.

只要对象被实例字段 classObject 引用,就可以将此示例应用于您的示例,它不会比包含引用的 Test 实例更早地被垃圾回收。

但请注意,当应用于使用 Test 实例的代码时,仍然允许使用规范中提到的激进优化。只要两个,即 Test 实例和引用的对象一起被收集,就可能会出现比预期更早的释放行为。在这种情况下,适用于§12.6 的以下方面:

Java编程语言对finalize方法调用没有排序规定。Finalizer的调用顺序可能是任意的,甚至可以并发。

因此,完全有可能在“内部”对象的终结器被调用之前,Test 实例比由 classObject 引用的对象更早地被回收。唯一的保证是,在内部对象的终结器运行时,外部对象是不可达的(或具有挂起或并发终结)。由于在您的示例中,两者都没有非平凡的终结器,因此这无关紧要...


3
你认为私有对象可能因为没有其他代码能够访问而立即被GC回收的想法确实有些道理,但是这会干扰Java内存管理的一般语义。例如,如果该对象实现了finalize方法,并且Java语义明确规定何时一个对象可以被垃圾回收,那么该finalizer方法将按照规范被调用。
此外,请注意该对象可能反过来引用其他对象,产生更加复杂的可能结果。更不用说该对象随时可以通过反射访问,即使没有任何代码可以进行赋值,使得该字段突然变为空也毫无意义。
总之,在更广泛的视角下,有很多原因说明你的优化想法行不通。

1
最强有力的理由是它已经被规范明确排除 - Holger

0
在下面的代码中,假设已经调用了amethod。在哪一行/点上,myObject最初引用的对象才有资格进行垃圾回收?
你的问题是无意义的,因为高级源代码和垃圾收集器所看到的低级表示(寄存器中的全局根,在堆栈和全局变量中)之间存在断开连接。
你的短语“有资格进行垃圾回收”可能意味着堆分配的内存块何时变得不可达。因此,只能通过对堆分配和生成的代码将保留引用的时间进行大量(可疑的)假设来回答您的问题。

0

这里没有任何对象能够被垃圾收集器收集,因为你为同一个对象创建了两个引用,而且你只给其中一个引用赋空值,但另一个引用仍然指向这个对象。


0

由于您正在使用 classObject 中的 myObject保持引用),因此它(通过 classObject 引用在内存中的对象)仅在 Test 实例被释放/卸载之前才能进行垃圾回收。


执行amethod方法后,myObject会变得可用于垃圾回收,但classObject不会。 - Luiggi Mendoza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接