对象何时有资格进行垃圾收集?

Posted

技术标签:

【中文标题】对象何时有资格进行垃圾收集?【英文标题】:When Is The Object Eligible For Garbage Collection? 【发布时间】:2012-10-30 17:54:15 【问题描述】:

在下面的代码中,假设 amethod 已被调用。 myObject 最初引用的对象在哪一点/哪一行符合垃圾回收条件?

class Test 
  private Object classObject;

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

如果classObjectamethod 具有public、protected、default 或static 的访问修饰符,它会影响对象符合垃圾收集条件的点吗?如果有,会受到怎样的影响?

我的第一个想法是,当 Test 对象符合垃圾回收条件时,该对象符合垃圾回收条件。 但话又说回来了。优化器可能知道永远不会读取 classObject,在这种情况下,classObject = myObject; 将被优化,myObject = null; 是它有资格进行垃圾收集的点。

【问题讨论】:

当你说“对象”时,你指的是Test类实例,Object classObject还是Object myObject @LuiggiMendoza "the Object" 总是指 myObject 最初引用的 Object。 【参考方案1】:

该对象不会成为垃圾回收的候选对象,直到对它的所有引用都被丢弃。 Java 对象是通过引用分配的,所以当你有

   classObject = myObject;

您为堆上的同一对象分配了另一个引用。所以这一行

   myObject = null;

只去掉一个引用。要使myObject 成为垃圾回收的候选对象,您必须拥有

  classObject = null;

【讨论】:

编译器是否不允许按照问题中的描述优化对 ivar 的分配? 基于多种因素,包括参考类型(弱与软),无论如何依赖此优化都不是直接(或安全)@KartickVaddadi @VaddadiKartick:规范声明确切地 不允许对堆变量进行这种优化。见this answer… 这个答案中提到的理论隐含地假设所有引用都是强引用。答案没有考虑其他类型的引用,例如软引用和弱引用。但是,就提供的示例而言,答案没有错。【参考方案2】:

来自书籍 OCA Java SE 7

一个对象被标记为有资格被垃圾回收时 无法再访问,当对象熄灭时可能会发生这种情况 的范围。当对象的引用变量为 分配显式空值或重新初始化。

【讨论】:

【参考方案3】:

Java 语言规范 (§12.6.1, Implementing Finalization ) 实际上准确地解决了这个问题:

可以设计优化程序的转换,将可到达的对象的数量减少到比那些天真地认为是可到达的要少。例如,Java 编译器或代码生成器可能会选择将不再使用的变量或参数设置为 null,以使此类对象的存储空间可能更快地被回收。

如果对象字段中的值存储在寄存器中,则会发生这种情况的另一个示例。然后程序可能会访问寄存器而不是对象,并且永远不会再次访问对象。这意味着该对象是垃圾。 …

但是

…请注意,这种优化只有在引用位于堆栈上而不是存储在堆中时才允许。

例如,考虑 Finalizer Guardian 模式:

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

如果子类覆盖 finalize 并且未显式调用 super.finalize,则终结器守护程序强制调用 super.finalize

如果允许对存储在堆上的引用进行这些优化,那么 Java 编译器可以检测到 finalizerGuardian 字段从未被读取,将其清空,立即收集对象,并提前调用终结器。这与本意背道而驰:当Foo 实例变得无法访问时,程序员可能想要调用Foo 终结器。因此,这种转换是不合法的:只要外部类对象可访问,内部类对象就应该是可访问的。

这个例子可以1:1的应用到你的例子中,只要对象被实例字段classObject引用,它就不会比包含引用的Test实例更早被垃圾回收。

但是,请注意,当使用Test 实例将其应用于代码时,规范中提到的积极优化仍然是允许的。只要 bothTest 实例和引用的对象被收集在一起,就可能发生早于预期的收集。在这种情况下,§12.6 中指定的以下方面适用:

Java 编程语言对 finalize 方法调用没有任何顺序。终结器可以按任何顺序调用,甚至可以同时调用。

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

【讨论】:

【参考方案4】:

您认为私有对象可能会立即被 GC,因为没有其他代码能够访问它的想法确实有一些吸引力,但这会与 Java 内存管理的一般语义相混淆。例如,如果该对象实现了finalize,并且 Java 语义明确规定了对象何时符合垃圾回收条件,则必须根据规范调用终结器方法。

还要注意,对象反过来可能会引用其他对象,可能会产生更复杂的结果。更不用说反射可以随时访问该对象,即使没有代码可以进行该分配,观察到的字段突然更改为 null 也是没有意义的。

总而言之,您的优化想法在更广泛的情况下行不通的原因有很多。

【讨论】:

最强烈的理由是it has been ruled out explicitly by the specification...【参考方案5】:

这里没有对象符合垃圾回收条件,因为您正在为同一个对象创建两个引用,并且您只为一个引用提供 null,但另一个引用仍然指向您的对象

【讨论】:

【参考方案6】:

由于您在classObject 中持有myObject引用保持),它(通过classObject 引用的内存中的对象)将无法用于垃圾收集,直到Test 的实例释放/卸载。

【讨论】:

在执行amethod 方法后,myObject 将可用于GC,但classObject 不会。【参考方案7】:

在下面的代码中,假设 amethod 已被调用。 myObject 最初引用的对象在哪一点/哪一行符合垃圾回收条件?

您的问题是荒谬的,因为您的高级源代码与垃圾收集器看到的低级表示(寄存器、堆栈和全局变量中的全局根)之间存在脱节。

您的短语“有资格进行垃圾收集”大概是指堆分配的内存块在什么时候变得无法访问。因此,您的问题只能通过对堆分配的内容以及生成的代码将保留引用的时间做出很多(可疑的)假设来回答。

【讨论】:

以上是关于对象何时有资格进行垃圾收集?的主要内容,如果未能解决你的问题,请参考以下文章

SCJP 想知道对象何时被垃圾收集的问题?

标记为垃圾收集vs符合java中的垃圾收集条件

什么时候在java中对字符串进行垃圾收集

单例对象是否符合垃圾回收条件?类中的静态字段如何收集垃圾?

如何/何时收集处理程序垃圾?

何时以及如何将 java 类加载器标记为垃圾收集?