当前位置: 首页 > 面试题库 >

Java弱引用寿命长

岳时铭
2023-03-14
问题内容

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

  • 我有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运行时都会解决弱可及性。


问题答案:

我终于可以检查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是否尚未运行。



 类似资料:
  • 本文向大家介绍详解JAVA 弱引用,包括了详解JAVA 弱引用的使用技巧和注意事项,需要的朋友参考一下 定义 弱引用是使用WeakReference创建的引用,弱引用也是用来描述非必需对象的,它是比软引用更弱的引用类型。在发生GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。 说明 弱引用,从名字来看就很弱嘛,这种引用指向的对象,一旦在GC时被扫描到,就逃脱不了被回收的命运。 但

  • 介绍 弱引用缓存。对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除。该类使用了WeakHashMap做为其实现,缓存的清理依赖于JVM的垃圾回收。 使用 与TimedCache使用方法一致: WeakCache<String, String> weakCache = CacheUtil.newWeakC

  • 我在静态处理程序中使用弱引用来避免内存泄漏,然而,有时这个引用会被取消,我不明白为什么。 静态处理程序在存储库类中定义,该存储库类有一个在后台执行操作的方法,接收回调以在操作完成时通知调用方: 处理程序的代码如下所示: 出于某种原因,有时是空的,我试图理解为什么。 活动代码如下所示: 正如您所见,我正在为回调创建一个匿名类,但没有人持有对它的引用。 这就是弱引用无效的原因吗? 谢谢。

  • 本文向大家介绍Java 中的弱引用是什么,包括了Java 中的弱引用是什么的使用技巧和注意事项,需要的朋友参考一下 Java里一个对象obj被创建时,被放在堆里。当GC运行的时候,发现没有任何引用指向obj,那么就会回收obj对象的堆内存空间。 换句话说,一个对象被回收, 必须满足两个条件: (1)没有任何引用指向它 (2)GC被运行。 在实际开发中,我们可以通过把所有指向某个对象的referec

  • 我有一个问题与生命周期参数的结构。我不能100%确定如何描述这个问题,但是我创建了一个显示编译时错误的简单案例。 我得到的错误是 我尝试了很多变体,只是无法编译这个东西。我在这里找到了另一个帖子(如何修复:预期的具体寿命,但找到了绑定的寿命参数),但似乎绕过了问题,而不是解决它。我真的不明白为什么这个问题会出现。该 有什么想法吗?谢谢你的帮助。

  • 本文向大家介绍深入理解Java中的弱引用,包括了深入理解Java中的弱引用的使用技巧和注意事项,需要的朋友参考一下 不久之前,我面试了一些求职Java高级开发工程师的应聘者。我常常会面试他们说,“你能给我介绍一些Java中得弱引用吗?”,如果面试者这样说,“嗯,是不是垃圾回收有关的?”,我就会基本满意了,我并不期待回答是一篇诘究本末的论文描述。 然而事与愿违,我很吃惊的发现,在将近20多个有着平均