【发布时间】:2015-04-21 19:30:30
【问题描述】:
我们有一个 Java 应用程序,它读取大量数据,但只将这些数据保留很短的时间。数据存储在“简单”集合中(HashMap、HashSet)。这些集合在处理数据时被清除(所以我调用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.wrap、com.sybase.jdbc4.utils.BufferPool.makeBuffer、java.util.GregorianCalendar.computeFields、java.util.jar.Manifest$FastInputStream.<init>等... -
@chrylis:
Java heap space,如果这就是你的意思。更新了原始问题。 -
您确定没有任何变化吗?一些 Java 更新或新版本的应用程序?这是莫名其妙的,所以必须考虑一切。作为一个黑客,我会尝试为进程提供更多内存,并且我肯定尝试
System.gc()(稍后在问题解决后将其删除)。 -
当然,GC 可以被破坏。但是,您自己的代码被破坏的可能性更大。添加
-XX:+HeapDumpOnOutOfMemoryError并使用 Ecilpse MAT 分析堆转储。
标签: java garbage-collection out-of-memory