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

时间:2011-11-16 02:30:21

标签: java memory-leaks ibm-midrange j9

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

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

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

enter image description here

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

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

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

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

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

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

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

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

3 个答案:

答案 0 :(得分:5)

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

然而,在此过程中,我已经证明AS / 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 :(得分:4)

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

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

  7. 希望这里有所帮助。

答案 2 :(得分:3)

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

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

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

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


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


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