在Mac OSX 5.8上,我有一个Java程序,它运行在100%CPU很长一段时间 - 几天或更长时间(这是一个模型检查器分析并发程序,因此或多或少的预期)。然而,它的虚拟内存大小,如OSX的活动监视器所示,在一天左右后变得非常庞大:现在它已经是16GB并且在不断增长。物理内存使用率大致稳定在1.1GB左右。
我想知道:16GB(并且还在增长)是一个可能会减慢我的程序的问题的迹象吗?
I start the program with "java -Xmx1024m -ea"
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-9M3326)
Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode)
感谢大家的建议。我将尝试在一些答案中给出的分析建议并返回(可能需要一段时间,因为多天的运行时间)。
在回答以下几点时,模型检查器几乎不执行I / O(仅打印语句,具体取决于调试设置)。在我使用的模式中它没有GUI。我不是模型检查器的主要作者(虽然我已经在它的一些内部工作),但我不相信它使用JNI。[< ---编辑:这是错的,详情如下]它不进行任何内存映射。另外,我不是要求Oracle / Sun的JVM创建大量线程(请参阅下面的解释)。
额外的虚拟内存没有导致模型检查器死亡,但是基于打印输出的频率,随着虚拟内存使用量的增加,它逐渐运行得越来越慢。 (也许这只是因为垃圾收集越来越多。)我计划在周一在Windows机器上试用它,看看是否会出现同样的问题。
一点额外的解释:我正在运行的模型检查器(JPF)本身就是一个几乎完整的JVM(完全用Java编写),它运行在Oracle / Sun的JVM下。当然,作为虚拟机,JPF非常专业化以支持模型检查。
这有点违反直觉,但这意味着即使我模型检查的程序设计为多线程,就Sun的JVM而言,只有一个线程:运行JPF的线程。 JPF模拟我的程序需要的线程作为其模型检查过程的一部分。
我相信斯蒂芬C已经指出了这个问题; Roland Illig给了我验证它的工具。我对JNI的使用是错误的。 JPF本身不使用JNI,但它允许插件和JNI被其中一个配置的插件使用。幸运的是,我可以使用纯Java的等效插件。其中一个的初步使用显示在过去几个小时内虚拟内存没有增长。感谢大家的帮助。
答案 0 :(得分:11)
我怀疑它也是泄漏。但它不能泄漏“正常”内存,因为-Xmx1024m选项限制了正常堆。同样,它不会是'permgen'堆的泄漏,因为permgen的默认最大大小很小。
所以我怀疑它是以下之一:
你正在泄漏线索;即正在创建线程但未终止。它们可能不是活动的,但每个线程都有一个堆栈段(默认为256k到1Mb,具体取决于平台),而不是在常规堆中分配。
您正在泄漏直接映射文件。它们映射到由常规堆外部的OS分配的内存段。 (@bestsss建议您查找泄露的ZIP文件句柄,我认为这是一个子案例。)
您正在使用一些泄漏malloc内存或类似内容的JNI / JNA代码。
无论哪种方式,内存分析器都可能隔离问题,或者至少消除一些可能性。
JVM内存泄漏也是可能的,但是在您明确消除您自己的代码和正在使用的库/应用程序中的可能原因之前,开始怀疑JVM是不明智的。
答案 1 :(得分:3)
由于您的应用程序不是实时应用程序,您可以执行以下操作:
jps -v
从该表中获取进程ID,并将其另存为pid
。
jmap -histo $pid > before-gc.hgr
jmap -histo:live $pid > after-gc.hgr
jstack -v $pid > threads.txt
threads.txt
文件告诉您此过程目前正在做什么。
堆使用直方图before-gc.hgr
和after-gc.hgr
告诉您完整垃圾收集可以释放多少内存。
也许你会从中得到一些关于发生了什么的提示。
答案 2 :(得分:0)
一旦完成对象,你是否正确NULL
所有引用?我想知道这是否是一个简单的内存泄漏...
我在C程序中看到过这种行为,它混合了大量不同大小的对象的分配和解除分配。你可以结束使用率不足的内存页面,但没有足够大的足够的漏洞可以用来满足新的内存请求。
这不仅会导致交换,而且会随着时间的推移而破坏引用的位置 - 交换会使更糟然后更糟糕。
C程序中的解决方案通常是交换到slab allocator,并将不同大小的对象保存在不同的内存页面上。没有更多奇数尺寸的洞,并且总是有一个指向空间的指针,无论你的对象是什么,它都是正确的尺寸。
当然,在像Java这样的托管环境中执行此操作可能很困难。人们希望JVM已经在做了。 (鉴于托管环境可以更新对新内存位置的引用并定期重新打包,即使是一种天真的内存分配方法也应该防止愚蠢的漏洞。)
出现问题的迹象通常来自交换流量。如果您的系统上看到大量交换流量,即使进程的RSS
(驻留集大小)保持稳定,那么您可能会为出现的运行执行大量磁盘IO运行完全在记忆中。在Linux和其他一些Unix系统上,您可以使用vmstat 1
命令找到交换流量信息:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 0 727392 351128 3846980 0 0 13 16 41 73 3 1 96 0
0 0 0 727392 351128 3846988 0 0 0 0 1267 4742 5 1 95 0
...
si
和so
列每秒显示块。 (嗯,您使用vmstat 1
或vmstat 2
选择的时间间隔等等。)
如果交换流量很低,那么您可能没什么可担心的。如果交换流量很高,你一定要试着找出为什么你正在交换。这需要更多的工作。 :)
答案 3 :(得分:0)
这对我来说听起来像是一个记忆漏洞。您应investigate使用堆分析器,例如VisualVM(随JDK一起提供)。
编辑:我忽略了你的堆限制。所以它实际上不是一个简单的Java内存泄漏(尽管如此) 可能由于某种原因,堆大小被忽略 - 仍然值得一试)。另一种可能性是你的应用程序占用了大量的非堆内存资源,例如本机GUI对等体(隐藏但未被废弃的窗口),线程(线程对象很小,但具有大堆栈内存,这会永远不会被释放,如果线程是例如创建但未启动)或IO(不知道如何在用完文件句柄之前占用如此多的内存)。 在更深层次上,它可能是sarnold编写的分配问题,或JVM本身内部的内存泄漏。
答案 4 :(得分:0)
如果jvisualvm没有报告过多的内存使用情况,则会变成OS X应用程序问题,其中应用程序是JVM。
我建议你改开这样的问题。
答案 5 :(得分:0)
如果不确切知道程序正在做什么,可能无法准确回答这个问题。也许它真的只是使用了很多内存......
在任何情况下,您都可以尝试使用VisualVM工具连接到该流程,并查看内存分配和释放的情况。
此外,还有很多-XX: options来调试垃圾收集,它们可能会提供更多信息。