长期存在的Java WeakReferences

我目前正在尝试在我的应用程序中诊断缓慢的内存泄漏。 我到目前为止的事实如下。

  • 我从应用程序的4天运行中有一个堆转储。
  • 这个堆转储包含~800个WeakReference对象,这些对象指向对象(所有相同的类型,我将这个问题称为Foo)保留40mb的内存。
  • Eclipse内存分析工具显示这些WeakReferences引用的每个Foo对象都不会被任何其他对象引用。 我的期望是这应该使这些Foo对象弱到可靠 ,因此它们应该在下一个GC收集。
  • 这些Foo对象中的每一个都有一个时间戳,表明它们是在4天运行过程中分配的。 在此期间我也有日志确认垃圾收集正在发生。
  • 我的应用程序正在创建大量的Foo对象,并且只有很小一部分在堆转储中以这种状态结束。 这告诉我,根本原因是某种竞争条件。
  • 我的应用程序使用JNI调用本机库。 JNI代码在初始化初始化期间调用NewGlobalRef 4次以获取对它使用的Java类的引用。

尽管只是被WeakReferences引用(根据Eclipse Memory Analyzer Tool),但是可能导致这些Foo类无法收集的原因是什么?

EDIT1:

@mindas我使用的WeakReference等效于以下示例代码。

public class FooWeakRef extends WeakReference { public long longA; public long longB; public String stringA; public FooWeakRef(Foo xiObject, ReferenceQueue xiQueue) { super(xiObject, xiQueue); } } 

Foo没有终结器,只要WeakRefs尚未被清除,任何终结器都不会被考虑。 当一个对象弱到达时,它不能最终确定。 有关详情,请参阅此页面 。

@kasten在对象可以最终化之前清除弱引用。 我的堆转储表明这没有发生。

@jarnbjo我引用了WeakReference Javadoc:

“假设垃圾收集器在某个时间点确定一个对象是弱可达的。那时它将primefaces地清除对该对象的所有弱引用以及对该对象可从其访问的任何其他弱可达对象的所有弱引用通过一系列强大而柔软的参考资料。“

这告诉我,GC应该检测到我的Foo对象“弱可达”和“当时”清除弱引用这一事实。

编辑2

@j flemm – 我知道40mb听起来并不多,但我担心4天内40mb意味着100天内4000mb。 我读过的所有文档都表明,弱可达的对象不应该闲置几天。 因此,我对如何在没有引用显示在堆转储中强烈引用对象的任何其他解释感兴趣。

当一些悬空的Foo对象存在时,我将尝试分配一些大对象,并查看JVM是否收集它们。 但是,此测试需要几天的时间来设置和完成。

编辑3

@jarnbjo – 我知道我不能保证JDK何时会注意到一个对象是弱可达的。 但是,我希望在4天的重负载下应用程序可以为GC提供足够的机会来注意我的对象是弱可达的。 4天后,我强烈怀疑其余的弱引用对象已经以某种方式泄露。

编辑4

@j flemm – 这真的很有趣! 只是为了澄清,你是说GC正在你的应用程序上发生并且没有清除Soft / Weak refs? 您能否告诉我有关您正在使用的JVM + GC配置的更多详细信息? 我的应用程序使用80%堆的内存条来触发GC。 我假设任何旧的GC的GC都会清除Weak refs。 您是否建议GC仅在内存使用率高于较高阈值时收集弱引用? 这个上限是否可配置?

编辑5

@j flemm – 关于在SoftRefs之前清除WeakRefs的评论与Javadoc一致,其中声明:SoftRef:“假设垃圾收集器在某个时间点确定一个对象可以轻柔地到达。那时它可以选择清除primefaces地对该对象的所有软引用以及对任何其他可通过一系列强引用访问该对象的软可引用对象的所有软引用。同时或稍后它会将那些新清除的软引用排入队列在参考队列中注册。“

WeakRef:“假设垃圾收集器在某个时间点确定一个对象是弱可达的。那时它将primefaces地清除对该对象的所有弱引用以及对该对象的任何其他弱可达对象的所有弱引用可以通过一系列强引用和软引用来访问。同时它将声明所有以前弱可达的对象都可以最终确定。同时或稍后它会将那些新清除的弱引用排入队列。在参考队列中注册。“

为清楚起见,你是说当你的应用程序有超过50%的可用内存时垃圾收集器运行,在这种情况下,它不能清除WeakRefs? 当你的应用程序有超过50%的可用内存时,为什么GC会运行? 我认为您的应用程序可能只是生成非常少量的垃圾,当收集器运行时它正在清除WeakRefs而不是SoftRef。

编辑6

@j flemm – 对于你的应用程序行为的另一个可能的解释是,正在收集年轻的gen,但是你的Weak和Soft refs都属于旧版本,只有在收集旧版本时才会被清除。 对于我的应用程序,我有统计数据显示正在收集旧的gen,这应该意味着WeakRefs被清除。

编辑7

我正在就这个问题开始赏金。 我正在寻找有关如何在GC发生时无法清除WeakRefs的任何合理解释。 如果答案是这是不可能的,那么理想情况下我会指向OpenJDK的相应位,它表示一旦确定一个对象是弱可达的,就会清除WeakRefs,并且每次GC运行时都会解析弱可达性。

我终于完成了检查Hotspot JVM源代码并找到了以下代码。

在referenceProcessor.cpp中:

 void ReferenceProcessor::process_discovered_references( BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc, AbstractRefProcTaskExecutor* task_executor) { NOT_PRODUCT(verify_ok_to_handle_reflists()); assert(!enqueuing_is_done(), "If here enqueuing should not be complete"); // Stop treating discovered references specially. disable_discovery(); bool trace_time = PrintGCDetails && PrintReferenceGC; // Soft references { TraceTime tt("SoftReference", trace_time, false, gclog_or_tty); process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true, is_alive, keep_alive, complete_gc, task_executor); } update_soft_ref_master_clock(); // Weak references { TraceTime tt("WeakReference", trace_time, false, gclog_or_tty); process_discovered_reflist(_discoveredWeakRefs, NULL, true, is_alive, keep_alive, complete_gc, task_executor); } 

函数process_discovered_reflist具有以下签名:

 void ReferenceProcessor::process_discovered_reflist( DiscoveredList refs_lists[], ReferencePolicy* policy, bool clear_referent, BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc, AbstractRefProcTaskExecutor* task_executor) 

这表明WeakRefs被ReferenceProcessor :: process_discovered_references无条件清除。

搜索process_discovered_reference的热点代码显示CMS收集器(我正在使用的)从以下调用堆栈调用此方法。

 CMSCollector::refProcessingWork CMSCollector::checkpointRootsFinalWork CMSCollector::checkpointRootsFinal 

每次运行CMS集合时,此调用堆栈看起来都会被调用。

假设这是真的,对长期存在的弱引用对象的唯一解释是要么是微妙的JVM错误,要么是GC没有运行。

您可能想检查是否已泄漏类加载器问题。 有关此主题的更多信息,请参阅此博客文章

您需要澄清FooWeakReference之间的联系。 案子

 class Wrapper extends WeakReference { private final T referent; public Wrapper(T referent) { super(t); this.referent = referent; } } 

与…非常不同

 class Wrapper extends WeakReferece { public Wrapper(T referent) { super(t); } } 

或其内联版本, WeakReference wr = new WeakReference(foo)

所以我假设你的情况与我在我的第一个代码片段中描述的情况不同。

正如您所说,您正在与JNI合作,您可能想要检查您是否有任何不安全的终结器。 每个终结器应该finally阻止调用super.finalize()并且它很容易滑动。

您可能需要告诉我们更多关于对象性质的信息,以提供更好的想法。

@iirekm No:WeakReferences比SoftReferences“弱”,这意味着在SoftReference之前,WeakReference总是被垃圾收集。

这篇文章中的更多信息: 了解Java的参考类:SoftReference,WeakReference和PhantomReference

编辑:(阅读评论后)是的,弱的参考文献比SoftReferences“弱”,错字。 :S

这里有一些用例可以进一步说明这个主题:

  • SoftReference :内存缓存(对象保持活动状态,直到VM认为没有足够的堆内存)
  • WeakReference :自动清除监听器(在被认为是弱可达之后, 在下一个GC周期清除对象)
  • PhantomReference :在处理exception大的对象时避免内存不足错误(在参考队列中调度时,我们知道清除主机对象,安全地分配另一个大对象)。 可以把它想象成一个finalize()替代方案,但却无法将死对象带回生活(正如你可能最终确定的那样)

这就是说,没有什么能阻止虚拟机(如果我错了,请纠正我)让弱可达物体保持活着,只要它没有内存不足(如原作者的情况)。

这是我能找到的最好的资源: http : //www.pawlan.com/monica/articles/refobjs/

编辑2:在PhantomRef中清除前面添加“待”

我不熟悉Java,但你可能正在使用分代垃圾收集器 ,它将保持你的Foo和FooWeakRef对象单独(不收集),只要

  • 他们过去了老一辈
  • 有足够的内存来分配年轻一代的新对象

指示垃圾收集发生的日志是否区分主要和次要集合?

对于声称在软引用之前清除了弱引用的非信徒:

 import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; public class Test { /** * @param args */ public static void main(String[] args) { ReferenceQueue q = new ReferenceQueue(); Map, String> referenceToId = new HashMap, String>(); for(int i=0; i<100; ++i) { Object obj = new byte [10*1024*1024]; // 10M SoftReference sr = new SoftReference(obj, q); referenceToId.put(sr, "soft:"+i); WeakReference wr = new WeakReference(obj, q); referenceToId.put(wr, "weak:"+i); for(;;){ Reference ref = q.poll(); if(ref == null) { break; } System.out.println("cleared reference " + referenceToId.get(ref) + ", value=" + ref.get()); } } } } 

如果你用-client或-server运行它,你会发现软引用总是在弱引用之前被清除,这也与Javadoc一致: http : //download.oracle.com/javase/1.4.2/docs/ API /爪哇/郎/ REF /包summary.html#可达性

通常,软/弱引用与Maps一起使用以生成各种缓存。 如果你的Map中的键与==运算符(或来自Object的unoverriden .equals)进行比较,那么最好使用在SoftReference键上运行的Map(例如来自Apache Commons) – 当对象’消失’时,任何其他对象都不会在’==’意义上与旧的相等。 如果将Map的键与高级.equals()运算符(如String或Date)进行比较,则许多其他对象可能与“消失”对象匹配,因此最好使用标准WeakHashMap。

请尝试使用SoftReference。 Javadoc说:在虚拟机抛出OutOfMemoryError之前,所有对软可访问对象的软引用都保证已被清除。

WeakReference没有这样的保证,这使它们更适合缓存,但有时SoftReferences更好。