Java程序的内存消耗问题

我有一个在我的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

top显示以下内容:

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

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

我的第一个问题是:

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

我的老问题是:

  • 为什么顶部输出中没有给出程序的内存消耗?

  • 如何找出分配如此多内存的内容?

  • 如果堆没有't the problem, is the only 2901164 the stack? (the stack shouldn'是一个问题,因为没有深"method call depth")

  • 外部资源作为数据库连接怎么样?

回答(8)

3 years ago

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

一些建议:

  • 有一些细节如何在本机代码here中查找内存泄漏 . 好read也 .

  • 像往常一样,确保正确关闭所有资源(文件,流,连接,线程等) . 其中大多数都是在某些时候调用本机实现,因此消耗的内存可能无法在JVM中直接显示

  • 检查操作系统级别消耗的资源 - 打开文件的数量,文件描述符,网络连接等 .

3 years ago

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

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

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

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

另外一个提示,确保你所有的关闭操作都在最后的块中如果异常使您退出正常的控制流程 . 众所周知,这也会引起这种问题 .

3 years ago

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

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

3 years ago

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

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

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

3 years ago

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

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

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

3 years ago

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

3 years ago

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

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

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

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

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

3 years ago

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

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