内存泄漏,没有对象数量或大小增加

在IBM iSeries系统上,我运行了一个Java程序 – 一个带有Web服务器组件的应用程序服务器,所有这些都是内部开发的。 在32位或64位J9 JVM(IBM Technology for Java)上运行时,我有内存泄漏的症状。

请注意,在iSeries经典JVM,多个Sun / Oracle JVM和Linux JVM上运行此软件时不会出现任何问题。 哎呀,我经常在我妻子的入门级笔记本电脑上运行相同的软件几周,而我正在我的网站上工作 – 我可以向你保证,如果它泄漏了内存,就会注意到那件事。

如果我只是让一个普通的系统运行空闲,没有配置应用程序(基本上只是消息传递系统和Web服务器),堆只会继续缓慢增长,导致随着时间的推移分配更多的内存,每个GC周期都没有相当于收集到以前的水平。 对于没有问题的JVM,模式完全相同,除了那些GC扫描总是将堆减少到之前的GC级别。

在此处输入图像描述

但是,如果我在稳定后在启动时拉出JVM系统转储,并且在分配的堆显着增长之后进行后续转储,则差异比较表明运行一周后不再可达的对象比启动时更多。 最近的一个,一周后显示6个额外的类加载和一些明确相关的对象。 对所有活体物体的彻底评论都没有显示出任何让我意想不到的东西。

我已经尝试了优化的吞吐量和世代并发的垃圾收集器。

因此,根据作业的堆大小,我们似乎正在泄漏,并且根据堆转储,没有任何泄漏。

没有调用JNI方法(除了作为核心JVM的一部分运行的本机代码),它肯定是正在增长的堆 – 我可以清楚地看到IBM WRKJVMJOB信息以及在我的控制台中使用JMX bean报告日志文件。

到目前为止,我无法使用JVisualVM之类的JMX工具连接到活动JVM,因为尽管在正确配置时创建了侦听套接字,但是连接被拒绝,显然是在协议级别(TCP / IP堆栈显示已接受的连接,但是JVM反弹它)。

我很困惑,不知道下一步该往哪里去。

编辑:只是为了澄清; 这些结果都是使用未经检测的JVM,因为我无法获得对此JVM的JMX访问权限(我们正在与IBM合作)。

编辑2011-11-16 19:27:我能够在1823个GC循环中提取GC活动报告,其中包括Soft / Weak / PhantomReference计数的特定计数; 这些数字并没有出现失控增长的迹象。 然而,小物体的终身空间显着增长(大物体的终身空间是空的)。 它从9M增长到36M。

在我的程序中消除了一些粗心的内存浪费(虽然没有任何泄漏),并且为我们的工作负载更好地调整了GC,我已经将失控的内存使用降低到可容忍的水平。

但是,在此过程中,我已经certificateAS / 400上使用的IBM J9 JVM(也称为iSeries,Systemi,i5等)的泄漏总量为1336字节/分钟,总计2 MB /天。 我可以通过从“单行”测试程序一直到我们的应用程序服务器的各种程序来观察这种泄漏。

单线测试程序是这样的:

public class ZMemoryLeak2 extends Object { static public synchronized void main(String... args) { try { ZMemoryLeak2.class.wait(0); } catch(InterruptedException thr) { System.exit(0); } } } 

除了通过JMX API监视内存使用之外什么都没做的单独测试程序最终显示1336 B以1分钟的间隔泄漏,永远不会被回收(好吧,在运行2周后没有回收)。 OP注意:实际上JVM的每个变体的数量都略有不同。

更新2012-04-02 :几周前,IBM将其作为一个错误接受; 它实际上是在去年年中的Java 5中找到并修补的,Java 6的补丁预计将在下一周或两周内发布。

好问题。 以为我会把我的一些评论转化为答案。

  1. 你提到空闲系统在内存方面有所增长。 这是一个重要的信息。 有一些内部预定作业(自动化,定时器等)或外部进程监视正在导致对象带宽。 我会考虑关闭监控以查看图表是否受到影响。 这可以帮助您确定哪些对象是问题的一部分。

  2. 当对象处于负载状态时,我怀疑存在一定量的对象带宽。 您的最终问题可能是IBM JVM没有处理内存碎片以及其他JVM – 我对此感到惊讶。 我会与他们一起尝试各种其他GC选项,看看你如何解决这个问题。 我认为如果你编写了一个测试服务器来完成一大堆内存操作,并且看看内存使用量是否会增加,那么这很容易模拟。 这可能表明是时候迁离IBM JVM了。 再次,这会让我感到惊讶,但如果你说的是真的,对象的数量或大小不会增长……

  3. 我会看一下各种内存部分的图表。 我怀疑你看到老一代空间上下起伏,幸存者稳步涓涓细流。 如果确实对象的数量没有变化,那么@Stephen必须对其内部大小或其他工作正确。 也许对象会计由于某种原因未能全部报告。

  4. 我发现内存选项卡上的gc JMX按钮执行更完整的扫描。 它应该等同于您尝试使用的System.gc() 。 仅供参考。

  5. 打开GC日志输出以查看是否可以看到任何模式是很好的: http: //christiansons.net/mike/blog/2008/12/java-garbage-collection-logging/和http:// java。 sun.com/developer/technicalArticles/Programming/GCPortal/

  6. 您是否有机会在改变监控或内部自动化的情况下提高服务器上的事务吞吐量? 如果您看到内存图表的斜率发生变化,那么您就知道它是基于事务的。 如果没有,那么你的问题就在别处。 同样,这是为了帮助您找到可能导致问题的对象。

希望这里的东西是有帮助的。

一种可能的解释是,您正在看到使用WeakReference或类似方法实现的缓存中对象的构建。 场景如下:

  • 您在图中看到的GC循环是新空间的集合,并且不会导致引用被破坏。 因此缓存继续增长并使用更多堆空间。

  • 拍摄快照时,会导致运行完整的GC(可能)会中断引用,并释放缓存的对象。

(注意“也许”。我不确定这个解释是否有用……)


另一种可能的解释是您的应用程序具有相同数量的对象,但其中一些对象更大。 例如,您可能有一些基本类型的数组,您可以使用更大的大小重新分配。 或者StringBuilder / StringBuffer不断增长。 或者(在某些情况下)一个不断增长的ArrayList或类似的东西。


你知道,你可能在这里追逐幽灵。 可能是系统转储正在说实话,根本没有存储泄漏。 您可以通过将堆大小减少到真正的内存泄漏可能相对较快地引发OOME的程度来测试该理论。 如果我不能以这种方式激怒OOME,我会倾向于把它写成一个有趣的好奇心……然后转向一个真正的问题。