23

在 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,因为我无法通过 JMX 访问该 JVM(我们正在与 IBM 合作)。

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

4

3 回答 3

5

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

然而,在这个过程中,我已经证明在 AS/400(又名 iSeries、Systemi、i5 等)上使用的 IBM J9 JVM 有 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 的补丁预计将在未来一两周内发布。

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

希望这里有帮助。

于 2011-11-16T20:52:07.163 回答
3

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

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

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

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


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


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

于 2011-11-16T03:08:01.990 回答