【问题标题】:JVM garbage collector seems to failJVM 垃圾收集器似乎失败了
【发布时间】:2015-04-21 19:30:30
【问题描述】:

我们有一个 Java 应用程序,它读取大量数据,但只将这些数据保留很短的时间。数据存储在“简单”集合中(HashMapHashSet)。这些集合在处理数据时被清除(所以我调用coll.clear() 而不是coll=null)。循环(读取-处理-清除)继续,直到处理完“所有数据块”。一定时间后,会有“新的块”,整个事情又开始了。

这个过程已经在服务器上运行了几个星期,没有任何问题。 然而,今天,在计划重启后,它一次又一次地崩溃,并出现OutOfMemoryError: Java heap space(并由监控进程自动重启)。

我使用远程调试器和 jvisualvm 工具连接到该进程,以尝试查找是否(以及在何处)可能存在内存泄漏。虽然处理线程在调用clear() 后立即暂停(由调试器),但我使用jvisualvm 工具强制gc。正如我所料,它几乎清除了整个堆(仅使用了 4MB)。下一个周期:同样的行为,在clear 之后几乎没有使用堆,等等......最后,进程没有没有内存不足了!

在我看来,垃圾收集器似乎无法正常工作......

  • 我如何验证是否是这种情况?
  • 如果是这样,怎么会这样?
  • 我应该在clear() 方法之后调用System.gc() 吗?

    但据我所知(并阅读here),这只是对虚拟机的“建议”;并且当堆快满时GC总是会收集所有可能的垃圾;并且应该避免这样的电话:-)...

(我们在 Solaris 上以服务器模式运行 Java 1.6.0_51-b11,没有特殊的 GC 选项)

编辑在分析堆转储后:

我们的代码有这样的结构:

final DataCollector collector = ...
while (!collector.isDone()) {
    final List<Data> dataList = collector.collectNext();
    for (final Data data : dataList) {
        // process data...
    }
}

OOMError 发生在执行 collector.collectNext() 方法时。

看起来堆仍然包含while循环的上一个迭代的dataList变量(和所有Data对象)!

while 循环的局部变量没有被垃圾收集是正常行为吗?如果这是真的,我们必须为这个过程提供几乎两倍于严格需要的内存......

作为 hack/check,我在 for 循环之后添加了一行 dataList = null,但这并没有改变行为(仍然是 OOM,堆转储仍然显示相同的“双重分配”)。

(我想我们很幸运该进程没有更早崩溃。)

【问题讨论】:

  • 发布OutOfMemoryError的整个堆栈跟踪;它可以包含其他有用的消息。
  • “整个堆栈跟踪”是不可能的:进程崩溃了 +40 次,每次都有完全不同的堆栈跟踪(main() 之后的 5 次调用除外)。我。它也没有用:OOMError 出现的确切代码几乎可以是任何东西,在这里我看到它出现在java.nio.ByteBuffer.wrapcom.sybase.jdbc4.utils.BufferPool.makeBufferjava.util.GregorianCalendar.computeFieldsjava.util.jar.Manifest$FastInputStream.&lt;init&gt; 等...
  • @chrylis: Java heap space,如果这就是你的意思。更新了原始问题。
  • 您确定没有任何变化吗?一些 Java 更新或新版本的应用程序?这是莫名其妙的,所以必须考虑一切。作为一个黑客,我会尝试为进程提供更多内存,并且我肯定尝试System.gc()(稍后在问题解决后将其删除)。
  • 当然,GC 可以被破坏。但是,您自己的代码被破坏的可能性更大。添加 -XX:+HeapDumpOnOutOfMemoryError 并使用 Ecilpse MAT 分析堆转储。

标签: java garbage-collection out-of-memory


【解决方案1】:

我遇到了垃圾收集器在一定时间后退出并发生错误 OOME 的问题。当您有一个带有很多圆圈的复杂对象链时,往往会发生这种情况。解决方案是使用-XX:-UseGCOverheadLimit 标志告诉垃圾收集器不要放弃。

【讨论】:

  • 我不同意两次: 1. GC 对复杂链没有问题,尤其是在无法访问的情况下(GC 并没有真正收集垃圾,而是捡起非垃圾,剩下的是空闲内存)。 2. 不是真的假OOME,是收集内存时间长,收益小的情况。这意味着应用程序几乎没有机会运行。
  • @maaartinus 这是 Oracle/Sun 针对各种错误推荐的解决方法。特别是有一个令人讨厌的 SoftReference 对象导致事物永远不会被垃圾收集,但还有其他各种情况。其中许多已在较新的 JVM 中修复/调整,但 OP 显然使用的是较旧的 JVM,其中可能尚未修复。一旦 GC 在第一次顺利运行时克服了“复杂性”。它不会像您建议的那样导致应用程序冻结。
  • 我知道 SoftReference 错误,但不知道其他错误。每次我看到“GC 开销太高”时,情况只会在被抑制时变得更糟。但我的经验有限,所以你可能是对的。但是,OP 获得了“Java 堆空间”,这似乎是一个不同的问题。
  • @Necreaux:对于我能找到的关于该选项的信息,该选项只有在错误消息为GC overhead limit exceeded 时才会生效。就我而言,这是一个java heap space 错误,所以这无济于事......或者这不正确吗?另外,您能否指出这些 oracle/sun 建议和/或错误?
  • @Geronimo 是的,GC overhead limit exceeded 是它,但我已经看到它隐藏在堆栈跟踪中或链接在其他异常中。根据打印异常的方式,您可能看不到它。我的想法一直是这个选项不会受到伤害。 AFAIK 最坏的情况是,如果存在 GENUINE 内存泄漏,这只会使 JVM 在 OOME 之前稍微颠簸一下,而不是立即抛出它。当您没有任何其他好主意时,也可以尝试一下。
【解决方案2】:

我有同样的问题,我所做的是:

通过以下方式清理您的项目:Project &gt; Clean &gt; Clean all projects (tick this option)

如果问题仍然存在,您需要重构代码,并消除内存泄漏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-06
    • 2013-12-03
    • 2019-11-12
    • 2023-03-07
    • 2011-02-23
    • 1970-01-01
    • 2018-01-08
    • 2015-04-03
    相关资源
    最近更新 更多