关于内存泄漏:Java WeakReferences长期存在

Long lived Java WeakReferences

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

  • 我有一个4天运行的应用程序的堆转储。
  • 此堆转储包含~800个weakreference对象,这些对象指向保留40MB内存的对象(所有类型都是相同的,为了解决此问题,我将调用foo)。
  • Eclipse内存分析工具显示,这些weakreferences引用的每个foo对象都没有被任何其他对象引用。我的期望是这将使这些foo对象弱可访问,因此它们应该在下一个GC中收集。
  • 每个foo对象都有一个时间戳,表明它们是在4天的运行过程中分配的。在这段时间内,我还保存了日志,以确认正在进行垃圾收集。
  • 我的应用程序正在创建大量的foo对象,其中只有很小的一部分最终处于堆转储中的这种状态。这对我来说,根本原因是某种种族状况。
  • 我的应用程序使用JNI调用本地库。JNI代码在开始一天的初始化过程中调用NealGualReF 4次,以获得对它使用的Java类的引用。

什么可能导致这些foo类不被收集,尽管只是被weakreferences引用(根据Eclipse内存分析器工具)?

Eddi1:

敏达斯我使用的weakreference等价于下面的示例代码。

1
2
3
4
5
6
7
8
9
10
11
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没有被清除。当对象可弱访问时,它是不可终结的。有关详细信息,请参阅本页。

@在对象可完成之前清除weakreferences。我的堆转储显示没有发生这种情况。

@Jarnbjo I指的是Weakreference JavaDoc:

"假设垃圾收集器在某个时间点确定对象是弱可访问的。此时,它将原子性地清除所有对该对象的弱引用,以及所有对任何其他弱可到达对象的弱引用,从这些弱可到达对象可以通过一系列强引用和软引用来访问该对象。"

这对我来说意味着GC应该检测到这样一个事实,即我的foo对象是"弱可到达"的,并且"在当时"清除弱引用。

编辑2

@我知道40毫巴听上去不怎么样,但我担心4天40毫巴意味着100天4000毫巴。我读过的所有文档都建议,那些弱可及的对象不应该在这里停留几天。因此,我对如何在没有引用出现在堆转储中的情况下对对象进行强引用的任何其他解释感兴趣。

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

编辑3

@Jarnbjo——我理解我无法保证JDK何时会注意到对象是弱可及的。但是,我希望在4天的高负载下的应用程序能够提供足够的机会让GC注意到我的对象是弱可访问的。4天后,我强烈怀疑剩余的弱引用对象是否被泄露了。

编辑4

@J弗莱姆-真有趣!只是为了澄清一下,您是说GC正在您的应用程序上发生,并且没有清除软/弱引用吗?你能给我更多关于你使用的是什么JVM+GC配置的细节吗?我的应用程序正在使用堆80%的内存条来触发GC。我假设老一代的任何GC都能清除弱引用。您是否建议GC仅在内存使用率高于较高阈值时收集弱引用?此上限是否可配置?

编辑5

@J flemm-您关于在SoftRefs之前清除weakerfs的评论与javadoc一致,后者指出:SoftRef:"假设垃圾收集器在某个时间点确定对象是可以软访问的。那时,它可以选择原子性地清除所有对该对象的软引用,以及所有对任何其他软访问对象的软引用,从这些软访问对象可以通过一系列强引用访问。同时或稍后,它将把那些新清除的、注册到引用队列中的软引用排队。"

Weakref:"假设垃圾收集器在某个时间点确定对象是弱可及的。此时,它将原子性地清除所有对该对象的弱引用,以及所有对任何其他弱可到达对象的弱引用,从这些弱可到达对象可以通过强和软引用链访问。


我终于开始检查Hotspot JVM源代码并找到以下代码。

在referenceprocessor.cpp中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
  }

函数进程"发现的"reflist具有以下签名:

1
2
3
4
5
6
7
8
9
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_founded_reference显示CMS收集器(我正在使用的)从以下调用堆栈调用此方法。

1
2
3
CMSCollector::refProcessingWork
CMSCollector::checkpointRootsFinalWork
CMSCollector::checkpointRootsFinal

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

假设这是真的,那么对于长期存在的弱引用对象的唯一解释就是一个微妙的JVM错误,或者如果GC没有运行的话。


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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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<Object> q = new ReferenceQueue<Object>();
    Map<Reference<?>, String> referenceToId = new HashMap<Reference<?>, String>();
    for(int i=0; i<100; ++i) {
        Object obj = new byte [10*1024*1024];    // 10M
        SoftReference<Object> sr = new SoftReference<Object>(obj, q);
        referenceToId.put(sr,"soft:"+i);
        WeakReference<Object> wr = new WeakReference<Object>(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());
        }
    }
}
}

如果您使用客户端或服务器运行它,您将看到软引用在弱引用之前总是被清除,这也与JavaDoc:http://liel.Oracle .COM/JavaSe/1.4.2/DOCS/API/Java/Lang/Ref/Cube摘要相一致。

通常软/弱引用与映射一起使用,以生成各种缓存。如果将映射中的键与==operator(或unoverriden.equal s from object)进行比较,那么最好使用在软引用键上操作的映射(例如,从apache commons),当对象"消失"时,其他对象在==意义上永远不会与旧对象相同。如果将映射的键与高级.equals()运算符(如字符串或日期)进行比较,则许多其他对象可能与"正在消失"的对象匹配,因此最好使用标准的weakhashmap。


我不熟悉Java,但您可能使用的是一个通用垃圾回收器,它将只保留您的FoO和FooeAkrf对象(不收集)。

  • 他们在上一代中过世
  • 有足够的内存来分配年轻一代的新对象

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


@IIREKM否:weakreference比softreference"弱",这意味着weakreference总是在softreference之前被垃圾收集。

在这篇文章中的更多信息:理解Java的引用类:软引用、弱引用和幻像引用

编辑:(阅读评论后)是的,弱引用肯定比软引用"弱",打字错误。的S

下面是一些用例来进一步说明这个主题:

  • SoftReference:内存缓存(对象保持活动状态,直到VM认为堆内存不足)
  • weakreference:auto clearing listeners(对象被认为是弱可到达的,应该在下一个GC循环中清除)
  • phantomreference:在处理异常大的对象时避免内存不足错误(在引用队列中调度时,我们知道主机对象将被清除,可以安全地分配另一个大对象)。把它当作finalize()的替代方法,而不具备使死对象恢复生命的能力(就像finalize可能的那样)

也就是说,没有什么能阻止虚拟机(如果我错了,请纠正我)让弱可到达的对象在没有耗尽内存的情况下(如orig)保持活动。作者的情况)。

这是我能找到的关于这个主题的最佳资源:http://www.pawlan.com/monica/articles/refobjs/

编辑2:在phantomref中的cleared前面添加"to be"


你需要澄清一下FooWeakReference之间的联系。案件

1
2
3
4
5
6
7
class Wrapper<T> extends WeakReference<T> {
  private final T referent;
  public Wrapper(T referent) {
    super(t);
    this.referent = referent;
  }
}

1
2
3
4
5
class Wrapper<T> extends WeakReferece<T> {
  public Wrapper(T referent) {
    super(t);
  }
}

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

所以我假设您的案例不像我在第一个代码片段中描述的那样。

正如您所说,您正在与JNI合作,您可能需要检查是否有任何不安全的终结器。每个终结器都应该有finally块调用super.finalize(),而且很容易出错。

你可能需要告诉我们更多关于你的物品的性质,以提供更好的想法。


尝试使用SoftReference。JavaDoc说:在虚拟机抛出OutofMemoryError之前,所有对软访问对象的软引用都将被清除。

weakreference没有这样的保证,这使得它们更适合缓存,但有时软引用更好。