2012-03-03 102 views
14

我有一个运行在我的Ubuntu 10.04机器上的Java程序,并且在没有任何用户交互的情况下反复查询MySQL数据库,然后根据从数据库中读取的数据构造img和txt文件D B。它使数以万计的查询和创建数以万计的文件。Java程序的内存消耗问题

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

为了找出分配这么多内存的内容,我想分析一个堆转储,所以我使用-Xms64m -Xmx128m -XX:+ HeapDumpOnOutOfMemoryError启动了该进程。

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

用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相关的)

我的老问题是:

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

尝试使用性能分析工具:http://stackoverflow.com/a/9205812/90909 – qrtt1 2012-03-03 13:31:32

+0

@ qrtt1:我使用了VisualVM,但是这表明堆不是问题(参见上文)。 – 2012-03-03 13:40:42

+1

您可以在这里找到答案,我认为http://stackoverflow.com/a/9306054/1140748和http://www.oracle.com/technetwork/java/hotspotfaq-138619.html#gc_oom(请参阅错误说明) – 2012-03-03 13:43:01

回答

0

因为一天之后我没问题(直到3月23日),而且我仍然找不到内存消耗的原因,所以我“务实”地解决了这个问题。

该程序引起问题基本上是一个“任务”的重复(即查询DB,然后创建文件)。对程序进行参数化是相对容易的,以便执行某个子集的任务,而不是全部。

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

对我来说这是一个足够的解决方案。如果你有类似的问题,你的程序有一个类似批处理的执行结构,这可能是一个实用的方法。

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

1

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

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

0

你的文件系统缓存可能是导致此,文件系统缓存会做大量的IO的时候吃了所有可用内存。您的系统性能不应受此行为的负面影响,内核将在进程请求内存时立即释放文件系统缓存。

+0

但是,内核不会交换正在运行的进程,直到缓存完全释放... – 2012-03-27 22:18:53

+0

我不认为这可能是我所看到的;发生这种情况时,我的系统确实受到了非常不利的影响它会消耗内存,直到使用所有可用的RAM,然后它将开始使用交换。正如你可以想象的那样,在这一点上,事情很快就会停顿下来! – Jan 2012-03-28 10:37:36

7

如果您的Java进程确实需要内存,并且VisualVM或内存转储中没有suspucios,那么它必须位于本机代码的某处 - 无论是在JVM还是在您正在使用的某些库中。例如,在JVM级别上,可能会使用NIO或内存映射文件。如果你的一些库正在使用本地调用,或者你的数据库没有使用类型4的JDBC驱动程序,那么泄漏可能在那里。

几点建议:

  • 还有一些细节如何找到本地代码here内存泄漏。好read也。
  • 像往常一样,请确保您正确关闭所有资源(文件,流,连接,线程等)。大多数的这些呼吁在一些点本地实现,因此消耗的内存在JVM可能不是直接可见的消耗在操作系统级别
  • 检查资源 - 打开文件,文件描述符,网络连接等数
+0

感谢您的回复。本机代码泄漏似乎是一种可能性。我们不使用任何我知道的本地库,甚至不使用JDBC。我们当然会尽量小心关闭所有文件等,但是我确实很难理解如何让这些资源打开可能会导致一个已被赋予16 GB堆的JVM消耗超过24 GB。当然,在文件句柄占用大量内存之前,操作系统会限制打开文件的数量? – Jan 2012-03-28 10:41:14

+0

如果不知道程序的内部部件,很难提供更好的建议。根据你为JVM分配的内存来判断,你必须做某种缓存或者从某处加载大量数据等等。另外,在原来的问题中,分配的内存量肯定不是16Gb,所以我不确定你是否指向相同或不相同的情况。 – maximdim 2012-03-28 12:00:54

2

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

所以找到这个诀窍是使用连续的堆转储,足够远,你已经注意到了,整个过程内存增长,但没有多大的分歧,一个工作吨已经上。您正在寻找的是堆中的Java对象数量不断增加并附加了本地内存。

这些可能是文件句柄,插座,数据库连接,或图像处理有可能直接适用于你只是仅举几例。

在更罕见的情况下,有一个由java实现泄露,即使在Java对象是垃圾回收原生资源。我曾经遇到过一个WinCE 5的bug,每个socket都关闭了4k。所以没有Java对象的增长,但有进程内存使用增长。在这些情况下,制作一些计数器并跟踪本机内存中对象的java分配与实际增长之间的关系会很有帮助。然后在一个足够短的窗口中,您可以查找任何相关性,并使用它们制作较小的测试用例。其他

一个提示,确保您的所有关闭操作是在finally块,以防万一有异常弹出你出你的正常流动。这已被认为会导致这类问题。

+0

嗨,詹姆斯,感谢您的评论,这很有趣。但正如我在@maximdim的回复中所说的,我们看到进程消耗的内存量稳定增加,比分配的堆量多出几千兆字节。由于其他原因,我们一直在认真追查尚未关闭的文件等,但是我看不到如何打开文件或套接字可能导致千兆字节的内存泄漏!此外,有问题的过程是一个服务器端的过程,所以不使用图形库,加载图像等... – Jan 2012-03-28 10:45:39

+0

如果您处理大量请求,4K的句柄可以快速加起来。还要确保你在finally块中关闭了所有的ResultSet实例。根据您的JDBC连接器版本,泄漏的ResultSet对象可能导致本机内存保留。 – 2012-03-28 12:18:10

+0

公平点,尽管它需要2,000,000泄漏文件句柄在4k一块泄漏8Gb,并且操作系统的限制比这个设置要低得多。另外,我们在这个过程中不使用JDBC。 – Jan 2012-03-28 13:37:32

1

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

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

也可能有其他可测试的情况,具体取决于您的应用程序做了什么。

1

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

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

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

0

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

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