Java“死”对象没有被垃圾收集

时间:2011-03-18 09:06:28

标签: java memory-leaks garbage-collection

我知道在Java中的垃圾收集过程中,没有任何对它们的引用的对象被标记为“死”,以便它们可以被垃圾收集器从内存中删除。

我的问题是,在垃圾收集阶段,所有“死”对象是否从内存中删除或者其中一些存活?为什么“死”对象会在垃圾收集阶段存活下来?

稍后编辑

感谢您的所有答案。我可以推断出“死”对象不会被删除的主要原因是由于垃圾收集器运行方式的时间或间隔限制。 但是,假设垃圾收集器可以到达所有“死”对象,我想知道是否有一种方法可以声明,引用,使用,取消引用等等。一个对象,以便它会以某种方式跳过删除阶段,即使它“死了”。我想也许属于具有静态方法或内部类的类的对象或类似的东西可能由于某种原因保存在内存中,即使它们没有引用它们。
这种情况可能吗?

谢谢

6 个答案:

答案 0 :(得分:9)

  

我的问题是,在垃圾收集阶段,所有“死”对象是否从内存中删除或者其中一些存活?为什么“死”对象会在垃圾收集阶段存活下来?

目前所有的HotSpot GC都是世代收藏家。引自维基百科:

  

“根据经验观察,在许多程序中,最近创建的对象也是最有可能快速无法到达的对象(称为婴儿死亡率或代际假设)。一代GC(也称为短暂的) GC)将对象划分为几代,并且在大多数周期中,将仅将一代子集的对象放入初始白色(被判定的)集合中。此外,运行时系统通过观察创建和覆盖来维护引用何时跨代的知识。当垃圾收集器运行时,它可以使用这些知识来证明初始白色集合中的某些对象无法访问而无需遍历整个参考树。如果生成假设成立,则会导致更快的收集周期同时仍在回收大多数无法到达的物体。“

这对您的问题意味着大多数GC周期仅收集年轻代中的垃圾对象。最老一代的垃圾对象可以在多个GC循环中存活......直到最终收集旧一代。 (在新的G1 GC中,显然老一代一次收集了一些......这可以进一步延迟回收。)

(理论上)无法访问的对象生存的其他原因包括:

  • 垃圾收集器将具有(未执行的)终结器的无法访问的对象附加到终结队列,以便在GC完成后进行处理。

  • 虚拟,弱或虚拟引用的对象实际上仍然可以访问,并且在GC完成后由各自的引用队列管理器处理。

  • 凭借JNI全局引用等可以访问的对象。 (感谢@bestss)

  • 存在各种与实例,类及其类加载器相关的隐藏引用。

  • 内部实例与其外部实例之间存在隐藏引用。

  • 从类中隐藏引用到实际的String对象,表示其字符串文字。

然而,这些都是可达性定义的结果:

  

“可到达的对象是任何可以从任何活动线程继续计算中访问的对象。” - JLS 12.6.1

值得注意的是,GC的规则对它们有一个保守的要素。他们说可访问的对象不会被删除,但他们并没有说会删除(严格地)无法访问的对象。这允许无法访问对象但运行时系统无法解决该问题的情况。


您的后续问题:

  

但是,假设垃圾收集器可以访问所有“死”对象,我想知道是否有一种方法可以声明,引用,使用,取消引用等等。这样一个对象会以某种方式跳过删除阶段,即使它“死了”。

“死”并不是一个定义明确的术语。如果垃圾收集器可以到达对象,则根据定义它们是可到达的。它们在仍然可以访问时不会被删除。

如果它们都已死并且可以访问(无论“死”意味着什么!)那么它们可以访问的事实意味着它们不会被删除。

你提出的建议没有意义。

  

我想也许属于具有静态方法或内部类的类的对象或类似的东西可能由于某种原因保留在内存中,即使它们没有引用它们。这种情况可能吗?

静态方法没有引用...除非它们碰巧在调用堆栈上。然后局部变量可能包含引用,就像任何其他方法调用一样。正常可达性规则适用。

静态字段是GC根,只要类本身存在。正常可达性规则适用。

从GC角度来看,内部类的实例与其他类的实例没有什么不同。可以在内部类实例中引用外部类实例,但这会导致正常的可达性。

总之,可达性有一些意想不到的“原因”,但它们都是可达性定义的逻辑结果。

答案 1 :(得分:3)

正如System.gc() javadoc所说

  

当控制从方法返回时   调用,Java虚拟机有   尽最大努力收回空间   来自所有丢弃的物品。

从中可以推断出对垃圾收集器的调用并不能确保回收所有未使用的对象。由于垃圾收集在实现之间可能完全不同,因此无法给出明确的答案。甚至还有java实现without any garbage collection

答案 2 :(得分:3)

无法收集无法访问的对象的一个​​可能的解释是时间。从Java 1.5开始,可以使用以下选项来限制JVM花费垃圾收集的时间......

  • -XX:MaxGCPauseMillis
  • -XX:GCTimeRatio=<nnn>

两个选项都详细解释了here

答案 3 :(得分:2)

  • “年轻”一代中有死亡物体,“老”一代中有死亡物体。如果GC在“次要GC”中执行,则仅收集来自年轻一代的死物。
  • 此外,您可以使用finalize()方法阻止VM通过从finalize()抛出异常来收集您的对象(至少,这是我理解的Object.finalize() javadoc:任何finalize方法抛出的异常导致暂停此对象的终结,但忽略)。

答案 4 :(得分:0)

未完全指定垃圾收集器的行为。如果特定实现选择不收集某些对象,则允许这样做。这可以避免在垃圾收集器中花费大量时间,这可能会对应用程序的运行产生不利影响。

答案 5 :(得分:0)

想象一下,你有一个包含数百万个小对象的集合,其中大多数都没有在其他任何地方被引用。如果清除了对该集合的唯一引用,您是否希望GC花费很长时间来清理那些数百万个小对象,或者您希望它在多次调用过程中这样做?在大多数情况下,后者对于应用程序会更好。