Java程序的内存消耗问题

时间:2012-03-03 13:21:44

标签: java memory

我有一个在我的Ubuntu 10.04机器上运行的Java程序,在没有任何用户交互的情况下,反复查询MySQL数据库,然后根据从DB读取的数据构造img和txt文件。它可以生成数以万计的查询并创建数万个文件。

经过几个小时的运行后,我机器上的可用内存(包括交换空间)已经完全耗尽。我还没有启动其他程序,并且在后台运行的进程不会消耗太多内存,也不会真正增加消耗。

为了找出分配如此多内存的内容,我想分析一个堆转储,所以我用-Xms64m -Xmx128m -XX:+ HeapDumpOnOutOfMemoryError开始了这个过程。

令我惊讶的是,情况和以前一样,经过几个小时后,程序正在分配所有的交换,这超出了给定的最大值128米。

使用VisualVM调试的另一个运行显示堆分配永远不会超过最大值128m - 当分配的内存接近最大值时,其中很大一部分会再次释放(我假设是垃圾收集器)。

因此,稳定增长的堆不会成为一个问题。

当内存全部耗尽时:

免费显示以下内容:

             total       used       free     shared    buffers     cached
Mem:       2060180    2004860      55320          0        848    1042908
-/+ buffers/cache:     961104    1099076
Swap:      3227640    3227640          0

顶部显示以下内容:

USER    VIRT    RES     SHR     COMMAND
[my_id] 504m    171m    4520    java
[my_id] 371m    162m    4368    java

(到目前为止,两个“最大”进程和唯一运行的java进程)

我的第一个问题是:

  • 如何在操作系统级别(例如使用命令行工具)找到分配如此多内存的内容? top / htop没有帮助我。在许多情况下,许多相同类型的微小进程占用了内存:有没有办法智能地总结类似的进程? (我知道这可能不是主题,因为它是Linux / Ubuntu问题,但我的主要问题可能仍然是与Java相关)

我的老问题是:

  • 为什么顶部输出中没有给出我程序的内存消耗?
  • 如何找出分配如此多内存的内容?
  • 如果堆不是问题,堆栈的唯一“分配因素”是什么? (该 堆栈不应该是一个问题,因为没有深层的“方法调用深度”)
  • 作为数据库连接的外部资源怎么样?

8 个答案:

答案 0 :(得分:7)

如果您的Java进程确实是占用内存的进程,并且VisualVM或内存转储中没有任何疑问,那么它必须位于本机代码中的某个位置 - 无论是在JVM中还是在您正在使用的某些库中。例如,在JVM级别上,如果您使用的是NIO或内存映射文件。如果你的某些库正在使用本机调用,或者你正在使用不为你的数据库键入4个JDBC驱动程序,那么可能存在泄漏。

一些建议:

  • 有一些细节如何在本机代码here中查找内存泄漏。好read也好。
  • 像往常一样,确保正确关闭所有资源(文件,流,连接,线程等)。其中大多数都是在某些时候调用本机实现,因此在JVM中可能无法直接看到消耗的内存
  • 检查操作系统级别消耗的资源 - 打开文件数,文件描述符,网络连接等。

答案 1 :(得分:2)

@ maximdim的回答是针对这种情况的一般建议。这里可能发生的是一个非常小的Java对象被保留,导致一些更大量的本机(OS级)内存闲置。 Java堆中不考虑此本机内存。 Java对象可能很小,以至于在Java对象保留将使堆压倒之前,您将很快达到系统内存限制。

所以找到这个的诀窍就是使用连续的堆转储,远远超过你已经注意到整个过程的内存增长,但不是那么远,以至于大量的工作已经开始。您正在寻找的是堆中的Java对象计数不断增加并且附加了本机内存。

这些可以是文件句柄,套接字,数据库连接或图像句柄,仅列举一些可能直接适用于您的文件句柄。

在更罕见的情况下,即使Java对象被垃圾回收,也会有java实现泄露的本机资源。我曾经遇到过一个WinCE 5漏洞,每个插座都关闭了4k漏洞。因此没有Java对象增长,但是有进程内存使用量增长。在这些情况下,制作一些计数器并跟踪具有本机内存的对象的Java分配与实际增长是有帮助的。然后在足够短的窗口上,您可以查找任何相关性并使用它们来制作更小的测试用例。

另外一个提示,请确保所有关闭操作都在finally块中,以防万一异常使您退出正常的控制流。众所周知,这也会引起这种问题。

答案 2 :(得分:1)

嗯......使用ipcs检查共享内存段是否未打开。检查JVM的打开文件描述符(/proc/<jvm proccess id>/fd/*)。在顶部,键入fpFp以显示交换并按使用交换任务列表排序。

这就是我现在能想出来的所有内容,希望它至少有一点帮助。

答案 3 :(得分:1)

正如@maximdim和@JamesBranigan指出的那样,可能的罪魁祸首是代码中的一些本机交互。但是,由于您无法准确追踪有问题的交互使用可用工具的位置,为什么不尝试使用强力方法呢?

您已经概述了一个两部分过程:查询MySQL和写入文件。这些事项中的任何一个都可以作为测试从流程中排除。测试一:消除查询并硬编码将返回的内容。测试二:执行查询,但不要费心写文件。你还有泄漏吗?

可能还有其他可测试案例,具体取决于您的应用程序的其他用途。

答案 4 :(得分:1)

您是否正在创建单独的线程来运行“任务”?用于创建线程的内存与Java堆是分开的。

这意味着即使您指定-Xmx128m,Java进程使用的内存可能会高得多,具体取决于您使用的线程数和线程堆栈大小(每个线程获得一个分配的堆栈,由-Xss)指定的大小。

最近工作的例子: 我们有一个4GB的Java堆(-Xmx4G),但操作系统进程耗费超过6GB, 也用完了交换空间。 当我用cat /proc/<PID>/status检查进程状态时,我注意到我们运行了11000个线程。 由于我们设置了-Xss256K,因此很容易解释:10000个线程意味着2.5GB。

答案 5 :(得分:0)

您的文件系统缓存可能导致此问题,文件系统缓存会在执行大量IO时占用所有可用内存。系统性能不应受此行为的不利影响,内核将在进程请求内存时立即释放文件系统缓存。

答案 6 :(得分:0)

由于当天没有活动我问了问题(直到3月23日),而且由于我仍然无法找到记忆消耗的原因,我“务实地”解决了这个问题。

导致问题的程序基本上是“任务”的重复(即查询数据库然后创建文件)。参数化程序相对容易,因此执行某个任务子集而不是所有任务。

所以现在我反复从shell脚本运行我的程序,在每个进程中只执行一组任务(通过参数参数化)。最后,所有任务都在执行,但由于单个进程只处理任务子集,因此不再存在内存问题。

对我来说这是一个充分的解决方案。如果您遇到类似的问题并且您的程序具有类似批处理的执行结构,那么这可能是一种实用的方法。

当我找到时间时,我会调查新的建议,希望找出根本原因(感谢您的帮助!)。

答案 7 :(得分:0)

你说你在创建图像文件是在创建图像对象吗?如果是这样,你完成后对这些对象调用dispose()吗?

如果我没记错的话,java awt想象对象会分配必须明确处理的本机资源。