一尘不染

Java弱引用寿命长

java

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

  • 我有4天运行该应用程序的堆转储。
  • 该堆转储包含约800个WeakReference对象,这些对象指向保留40mb内存的对象(所有对象都是同一类型,出于这个问题的目的,我将其称为Foo)。
  • Eclipse内存分析工具显示,这些WeakReferences引用的每个Foo对象均未被其他任何对象引用。我的期望是,这应使这些Foo对象弱可及,因此应在下一次GC中将其收集。
  • 每个Foo对象都有一个时间戳,表明它们是在4天的运行过程中分配的。在这段时间内,我还拥有一些日志,这些日志确认垃圾收集正在发生。
  • 我的应用程序正在创建大量的Foo对象,而只有极少数的对象在堆转储中处于这种状态。这向我暗示根本原因是某种种族状况。
  • 我的应用程序使用JNI来调用本机库。JNI代码在一天开始的初始化过程中会调用NewGlobalRef 4次,以获取对其使用的Java类的引用。

尽管仅由WeakReferences引用(根据Eclipse Memory Analyzer Tool),却可能导致这些Foo类无法收集的原因?

编辑1:

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

public class FooWeakRef extends WeakReference<Foo>
{
  public long longA;
  public long longB;
  public String stringA;

  public FooWeakRef(Foo xiObject, ReferenceQueue<Foo> xiQueue)
  {
    super(xiObject, xiQueue);
  }
}

Foo没有终结器,只要未清除WeakRefs,就不会考虑任何终结器。当对象弱可访问时,它不能完成。有关详细信息,请参见此页面

@kasten弱引用在对象完成之前被清除。我的堆转储表明这没有发生。

@jarnbjo我指的是WeakReference Javadoc:

“假设垃圾收集器在某个时间点确定某个对象是弱可到达的。那时它将自动清除对该对象的所有弱引用以及对该对象可从其到达的任何其他弱可达对象的所有弱引用。通过一系列强而有力的参考。”

这向我建议,GC应该检测到以下事实:我的Foo对象“弱可及”并且“当时”清除了弱引用。

编辑2

@j flemm-
我知道40mb听起来不多,但我担心4天之内40mb意味着100天之内4000mb。我阅读的所有文档都建议弱可达的对象不应在几天内徘徊。因此,我对任何其他有关如何在不将引用显示在堆转储中的情况下强烈引用对象的解释感兴趣。

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

编辑3

@jarnbjo-
我了解我无法保证JDK何时会注意到对象是弱可访问的。但是,我希望在4天的高负载下提供的应用程序将为GC提供足够的机会来通知我的对象很难到达。4天后,我强烈怀疑其余的弱引用对象已经以某种方式泄漏。

编辑4

@j flemm-真的很有趣!只是为了澄清,您是说GC正在您的应用程序上发生,并且未清除软/弱引用吗?您能给我更多有关您正在使用的JVM + GC
Config的详细信息吗?我的应用程序正在使用80%的内存条来触发GC。我以为老一代的任何GC都会清除弱引用。您是否建议仅在内存使用量高于较高阈值时,GC才会收集弱引用?此上限是否可以配置?

编辑5

@j flemm-
您关于在SoftRefs之前清除WeakRefs的评论与Javadoc一致,其中指出:SoftRef:“假设垃圾收集器在某个时间点确定可以软访问对象。那时,它
可以
选择清除从原子上讲,对该对象的所有软引用以及对所有其他可通过该对象通过强引用到达该对象的可软到达的对象的软引用,同时或在以后的某个时间,它将使那些新清除的软引用入队已在参考队列中注册。”

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

为了清楚起见,您是说垃圾回收器在您的应用程序具有50%以上的可用内存时运行,在这种情况下,它不会清除WeakRefs?当您的应用程序具有50%以上的可用内存时,为什么GC会全部运行?我认为您的应用程序可能只产生很少量的垃圾,当收集器运行时,它清除的是WeakRefs,而不是SoftRefs。

编辑6

@j flemm-
您的应用程序行为的另一种可能的解释是,正在收集年轻一代,但是您的弱引用和软引用都在旧一代中,并且仅在收集旧一代时才清除。对于我的应用程序,我有统计数据表明正在收集旧基因,这应该意味着WeakRefs已清除。

编辑7

我开始悬赏这个问题。我正在寻找有关在发生GC时如何无法清除WeakRef的任何合理的解释。如果答案是不可能的,那么我理想地希望指向OpenJDK的适当位,这些位表明WeakRefs在确定对象弱可及性后立即被清除,并且每次GC运行时都会解决弱可及性。


阅读 226

收藏
2020-12-03

共1个答案

一尘不染

我终于可以检查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无条件清除。

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

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

每次运行CMS集合时,都将调用此调用堆栈。

假设这是真的,那么对长期存在的弱引用对象的唯一解释将是微妙的JVM错误或GC是否尚未运行。

2020-12-03