GC是否足够聪明,可以删除引用但不再使用的对象?

时间:2018-08-01 14:44:17

标签: java garbage-collection

假设我有一个名为“ master”的对象,它拥有100个分别称为“ slave0”到“ slave99”的对象。 (这不是一个数组。我的“主”类中分别有100个字段,称为salve0到slave99。)现在,让我的程序首先读取一个文件,该文件包含“主”对象的序列化存储版本。但是,假设我的程序从不使用对象slave50到slave99。会发生什么? (我的猜测是,在读取/反序列化过程中,java程序将首先读取所有100个从属对象,并且在读取所有100个从属对象后 only ,它可能会选择在以下位置执行GC注意:对象“ master”仍在使用中,因此从技术上讲,父对象master仍在引用slave50到slave99的对象,因此,“ slave”到“ slave99”的对象将被GC删除并回收内存。并且父对象主对象仍在积极使用中。)

后续问题

因此,可以说我上面的猜测关于GC的工作方式是正确的;如果我的长时间运行的程序花了几分钟时间处理从属对象slave0到slave50,然后又进入另一个名为“ X”的最终(长时间运行)过程,则该过程仅处理从属对象slave0到第二个事件。然后,GC是否会意识到,即使从属对象master仍在引用slave25到slave50对象,并且即使仍在使用对象master,GC仍将足够聪明以摆脱它是从对象25到从属对象25,因为没有人会从“过程X”开始使用它?

2 个答案:

答案 0 :(得分:6)

在Java中,GC不会删除活动对象。当查看跟踪GC逻辑时,从活动线程可以访问对象时,该对象被视为活动对象(除非我们正在考虑使用更多奇异的引用类型,例如WeakReference)。在您的简单示例中,master对象中的所有字段都是可访问的,因为master对象本身可从main线程访问。

关于跟踪GC,您可以阅读各种文章:

答案 1 :(得分:2)

对此没有简单的答案。您说“对象'master'仍在使用”,但不是以这种方式。原则上,可以优化对象的读写字段,甚至可以调用对象上的方法,从而无需存储对象。

或者,如the specification所述:

  

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

     

如果对象字段中的值存储在寄存器中,则会出现另一个示例。然后,程序可能会访问寄存器而不是对象,而不再访问对象。这意味着该对象是垃圾。

如“ finalize() called on strongly reachable object in Java 8”中所述,这不仅仅是一个理论问题。

但是规范还说:

  

请注意,只有在引用位于堆栈上而不存储在堆中的情况下,才允许进行这种优化。

     

...   (在示例中)只要外部类对象可访问,内部类对象就应该是可访问的。

这暗示着,只要您的“主”对象引用了“ slave50”至“ slave99”,就可以将它们视为可访问的,只要将“主”对象视为可访问的即可,但原则上允许一起收集它们。但是根据相同的规则,如果优化的代码能够在不再次访问其内存的情况下运行,那么即使是仍在使用的“ slave0”到“ slave25”也可以被收集。

请注意,由于优化后的代码旨在表现得与原始代码一样,因此您的程序不会注意到它们之间的区别。

因此,即使未使用的对象“天真的被认为是可以到达的”,它们也具有检测功能,但是它们通常取决于方法代码的优化状态,因为垃圾收集器不会分析代码,而是JVM的优化器做。在这方面,局部变量作用域纯粹是编译时的事情。对于未优化的代码可能会发生,垃圾回收器有时会认为引用仍然存在,而局部变量从源代码的角度来看却超出范围。但是,与其他情况相比,这种情况更常见,即使未处理的局部变量在优化的代码中也会消失,即使从源代码的角度来看也是如此。无论哪种情况,从方法返回都会破坏整个堆栈框架,包括所有局部变量,因此您无需在返回之前将局部变量设置为null

最好的策略是永远不要插入任何明确的操作来“帮助垃圾收集器”,除非您遇到方案的实际问题,否则JVM无法充分处理。这些真的很少见。