Java程序的内存消耗问题
Posted
技术标签:
【中文标题】Java程序的内存消耗问题【英文标题】:Memory consumption issues of a Java program 【发布时间】:2012-03-21 17:34:58 【问题描述】:我有一个在我的 Ubuntu 10.04 机器上运行的 Java 程序,无需任何用户交互,重复查询 mysql 数据库,然后根据从数据库读取的数据构造 img 和 txt 文件。它会进行数以万计的查询并创建数以万计的文件。
运行几个小时后,我机器上的可用内存(包括交换空间)已完全用完。我没有启动其他程序,后台运行的进程不会消耗太多内存,也不会真正增加消耗。
为了找出分配这么多内存的原因,我想分析一个堆转储,所以我用 -Xms64m -Xmx128m -XX:+HeapDumpOnOutOfMemoryError 开始了这个过程。
令我惊讶的是,情况和以前一样,几个小时后,程序分配了所有的交换空间,远远超出了给定的 128m 的最大值。
用 VisualVM 调试的另一次运行显示堆分配永远不会超过最大值 128m - 当分配的内存接近最大值时,它的很大一部分会再次释放(我假设是垃圾收集器)。
因此,稳定增长的堆不会成为问题。
当内存全部用完时:
free 显示如下:
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 相关)我的老问题是:
为什么我的程序的内存消耗没有在顶部输出中给出? 如何找出分配这么多内存的原因? 如果堆不是问题,那么唯一的“分配因素”是堆栈吗? (这 堆栈应该不是问题,因为没有很深的“方法调用深度”) 作为数据库连接的外部资源呢?【问题讨论】:
尝试使用分析工具:***.com/a/9205812/90909 @qrtt1:我使用了 VisualVM,但这表明堆不是问题(见上文)。 你可以在这里找到答案我认为***.com/a/9306054/1140748 和oracle.com/technetwork/java/hotspotfaq-138619.html#gc_oom(见错误描述) 试试 MAT。它显示了内存使用位置的更多详细信息。 您有没有使用系统内存的劣质显卡?也许一些图像处理正在处理卡,它消耗的系统内存比你想象的要多,迫使其他进程进入交换? 【参考方案1】:如果您的 Java 进程确实是占用内存的进程,并且在 VisualVM 或内存转储中没有任何可疑之处,那么它必须位于本机代码中的某个位置 - 无论是在 JVM 中还是在您正在使用的某些库中。例如,在 JVM 级别上,如果您使用 NIO 或内存映射文件。如果您的某些库正在使用本机调用,或者您的数据库使用的不是类型 4 的 JDBC 驱动程序,则可能存在泄漏。
一些建议:
有一些细节如何在本机代码here 中查找内存泄漏。也不错read。 像往常一样,确保正确关闭所有资源(文件、流、连接、线程等)。其中大多数在某些时候调用本机实现,因此消耗的内存可能在 JVM 中不直接可见 检查操作系统级别消耗的资源 - 打开文件的数量、文件描述符、网络连接等。【讨论】:
感谢您的回复。本机代码中的泄漏似乎是一种可能性。我们不使用任何我知道的本地库,甚至不使用 JDBC。我们当然会尽量小心地关闭所有文件等,但我确实很难理解如何让这些资源保持打开状态会导致已获得 16 GB 堆的 JVM 消耗超过 24 GB。在文件句柄消耗那么多内存之前,操作系统肯定会限制打开文件的数量吗? 在不了解程序内部结构的情况下很难给出更好的建议。从您为 JVM 分配的内存来看,您必须进行某种缓存或从某处加载大量数据等。在原始问题中,分配的内存量肯定不是 16Gb,所以我不确定您是否指的是是否相同。【参考方案2】:@maximdim 的回答是针对这种情况的一般建议。这里可能发生的情况是,一个非常小的 Java 对象被保留,导致大量本机(操作系统级)内存挂起。 Java 堆中不考虑此本机内存。 Java 对象可能非常小,以至于您将在 Java 对象保留压倒堆之前达到系统内存限制。
因此,找到这一点的诀窍是使用连续的堆转储,相距足够远以至于您注意到整个进程的内存增长,但相距不远以至于已经进行了大量工作。您正在寻找的是堆中不断增加的 Java 对象计数并附加了本机内存。
这些可能是文件句柄、套接字、数据库连接或图像句柄,仅举几例可能直接适用于您。
在更罕见的情况下,Java 实现会泄漏本机资源,即使在 Java 对象被垃圾回收之后也是如此。我曾经遇到过一个 WinCE 5 错误,其中每个套接字关闭都会泄漏 4k。所以没有 Java 对象增长,但是有进程内存使用增长。在这些情况下,创建一些计数器并跟踪具有本机内存的对象的 java 分配与实际增长情况是有帮助的。然后在足够短的窗口内,您可以查找任何相关性并使用它们来制作更小的测试用例。
另一个提示,确保所有关闭操作都在 finally 块中,以防万一异常使您脱离正常的控制流。众所周知,这也会导致此类问题。
【讨论】:
嗨,詹姆斯,感谢您的 cmets,这很有趣。但正如我在对@maximdim 的回复中所说,我们看到进程消耗的内存量稳步增加,比分配的堆内存量多出许多 GB。由于其他原因,我们一直在仔细寻找尚未关闭的文件等,但我看不出让文件或套接字打开会导致千兆字节的内存泄漏!此外,有问题的进程是服务器端进程,因此不使用图形库、加载图像等...... 如果您要处理大量请求,一次处理 4K 的句柄可以快速增加。还要确保您也在 finally 块中关闭所有 ResultSet 实例。根据您的 JDBC 连接器版本,泄漏的 ResultSet 对象可能会导致本机内存保留。 公平点,虽然每块 4k 的 2,000,000 个泄漏文件句柄才能泄漏 8Gb,并且操作系统限制设置的远低于此。此外,我们不会在相关过程中使用 JDBC。 我很困惑。问题描述提到了 MySQL 查询。所以如果你不使用 JDBC 来做那件事,你在做什么?【参考方案3】:嗯...使用 ipcs 检查共享内存段是否未打开。检查 JVM 的打开文件描述符 (/proc/<jvm proccess id>/fd/*
)。在顶部,键入fpFp
以显示交换并按使用的交换排序任务列表。
目前我能想到的就这些了,希望对你有一点帮助。
【讨论】:
【参考方案4】:正如@maximdim 和@JamesBranigan 指出的那样,可能的罪魁祸首是您的代码中的一些本机交互。但是由于您无法使用可用工具准确追踪有问题的交互在哪里,您为什么不尝试蛮力方法呢?
您已经概述了一个由两部分组成的过程:查询 MySQL 和写入文件。这些事情中的任何一个都可以作为测试从过程中排除。测试一:消除查询并对本应返回的内容进行硬编码。测试二:做查询,但不要费心写文件。你还有泄漏吗?
可能还有其他可测试用例,这取决于您的应用程序还做了什么。
【讨论】:
【参考方案5】:您是否正在创建单独的线程来运行您的“任务”?用于创建线程的内存与 Java 堆是分开的。
这意味着即使您指定-Xmx128m
,Java 进程使用的内存也可能要高得多,具体取决于您使用的线程数和线程堆栈大小(每个线程分配一个堆栈,大小指定-Xss
)。
最近工作的例子:
我们有一个 4GB 的 Java 堆(-Xmx4G
),但是操作系统进程消耗了 6GB 以上的空间,
也用尽了交换空间。
当我使用cat /proc/<PID>/status
检查进程状态时,我注意到我们有 11000 个线程正在运行。
由于我们设置了-Xss256K
,这很容易解释:10000 个线程意味着 2.5GB。
【讨论】:
【参考方案6】:您的文件系统缓存可能是造成这种情况的原因,当执行大量 IO 时,文件系统缓存会耗尽所有可用内存。您的系统性能不应受到此行为的不利影响,当进程请求内存时,内核将立即释放文件系统缓存。
【讨论】:
但是在缓存完全释放之前内核不会交换正在运行的进程... 我认为这不是我所看到的;发生这种情况时,我的系统确实受到了非常不利的影响。它将消耗内存,直到使用完所有可用的 RAM,然后它将开始使用交换。可以想象,到那时,事情很快就会停止!【参考方案7】:由于在我提出问题的那天(直到 3 月 23 日)之后没有任何活动,并且由于我仍然找不到内存消耗的原因,所以我务实地“解决”了问题。
导致问题的程序基本上是“任务”的重复(即查询数据库然后创建文件)。参数化程序相对容易,以便执行某个任务子集而不是所有任务。
所以现在我从一个 shell 脚本重复运行我的程序,在每个进程中只执行一组任务(通过参数参数化)。最后,所有任务都在执行,但由于单个进程只处理一部分任务,因此不再存在内存问题。
对我来说,这是一个足够的解决方案。如果您有类似的问题并且您的程序具有类似批处理的执行结构,这可能是一种实用的方法。
当我找到时间时,我会研究新的建议,希望能找出根本原因(感谢您的帮助!)。
【讨论】:
【参考方案8】:你说你正在创建图像文件你是在创建图像对象吗?如果是这样,您是否在完成后对这些对象调用 dispose() ?
如果我没记错的话,java awt 想象对象分配必须显式释放的本机资源。
【讨论】:
以上是关于Java程序的内存消耗问题的主要内容,如果未能解决你的问题,请参考以下文章