如何断定程序具有内存泄漏
查看一个运行在 Windows NT 平台上的 Java 程序是否具有内存泄漏,你可以简单地在程序运行的时候去观察任务管理器中的内存设置。然而,在观察一些运行中的 Java 程序之后,你会发现,它们跟本地应用程序相比使用更多内存。我开发过的一些 Java 项目会启用 10 到 20 MB 的系统内存。与这个数字相比,本地的操作系统自带的 Windows Explorer 程序使用到 5 MB。
另外一个关于 Java 程序的内存使用要注意的是典型的运行在 IBM JDK1.1.8 JVM 上的程序似乎在其运行时不断吞噬了越来越多的系统内存。程序似乎永远不会返回一些内存给操作系统,直到一个非常大的物理内存分配给它。这会不会就是内存泄漏的迹象?
要明白是怎么回事,我们需要熟悉 JVM 是如何将系统内存使用作自己的堆的。在运行 java.exe 时,你可以使用一些特定的选项来控制垃圾收集的堆的启动容量和最大容量(分别是 -ms 和 -mx)。Sun 的 JDK 1.1.8 默认使用 1 MB 的启动设置和 16 MB 的最大设置。IBM JDK 1.1.8 默认使用机器物理内存容量的一半作为最大设置。这些内存设置对 JVM 发生内存溢出时的做法具有直接影响,这时 JVM 可能会继续增长堆内存,而不是等待一个垃圾回收的结束。
因此为了寻找并最终消除内存泄漏,我们需要比任务监视程序更好的工具。当你想检测内存泄漏的时候内存调试程序(参见下文的参考资料)可以派上用场了。这些程序通常会给你关于堆内存里对象的数量、每个对象实例的个数以及对象使用中的内存等一些信息。此外,它们还会提供很有用的视图,这些视图可以显示每个对象的引用和引用者,以便你跟踪内存漏洞的来源。
接下来,我将展示如何使用 Sitraka Software 的 JProbe 调试工具来检测和消除内存泄漏,希望会对你就如何部署这些工具并成功消除内存泄漏产生一些启发。
一个内存泄漏的例子
这个示例主要展示了我们部门开发的一个商业版应用的一个问题,这个问题在 JDK 1.1.8 上工作了几个小时后被测试人员找出来。这个 Java 应用程序的相关代码和包是由几个不同团队的程序员开发出来的。程序里出现的内存泄漏的原因,我怀疑,是由一些没有真正理解其他(团队)开发的代码的程序员所引起。讨论中的 Java 代码允许用户不必去写 Palm OS 本地代码来创建 Palm 个人数码助理应用。通过使用图形界面,用户可以创建表单,使用控件对它们进行填充,然后连接控件事件来创建 Palm 应用程序。测试人员发现,这个 Java 应用最终发生了内存溢出——表单和控件的创建和删除延时。开发人员并没有发现这个问题存在,因为他们的机器(相对 Palm)拥有着更多的物理内存。
为了讨论这个问题,我使用了 JProbe 来断定问题的存在。即使拥有 JProde 提供的强大工具和内存快照,调查仍然是一个繁琐的、反复的过程,它涉及先确定内存泄漏的原因,然后做出代码更改并验证其效果。
JProbe 有几个选项来控制在一次调试回话期间什么样的信息会被记录。经过一些试验后,我判定获取所需信息的最有效的方式是关掉性能数据收集,专注于捕获的堆数据。JProbe 提供了一个叫做运行时堆摘要的视图来显示 Java 应用程序在一段时间内使用的堆内存的数量。它同时也提供了一个工具栏按钮用来在需要时强制 JVM 执行垃圾收集 --在想要看一下一个类的给定实例不再为 Java 应用程序需要时是否会被垃圾收集,这个功能是很有用的。下图显示了在一段时间内使用的堆存储量。
在堆使用情况图中,蓝色部分表示已分配的堆空间量。我启动 Java 程序之后它达到了一个稳定点,我强制垃圾收集器执行,这由绿线之前的蓝色曲线的一个骤降表示(这条绿线表示一个检查点被插入)。接下来,我先是添加而后删掉了四个表单并再次调用垃圾收集器。检查点之后的蓝色曲线的水平线比检查点之前的蓝色曲线的水平线高的事实告诉我们很可能出现了内存泄漏,因为该程序已经回归其只有一个简单可见的表单的初始状态。我检查实例确认了泄漏。总之,结果表明 FormFrame 类(表单的主 UI 类)的数量在检查点之后增加了四个。