本系列的 第 1 部分 解决了基于 Eclipse 的富客户机应用程序性能问题的几个方面,包括 CPU、I/O 以及线程问题等。内存泄漏是导致性能问题的另一可能原因。本文阐述如何监视应用程序的内存使用,描述您在开发富客户机应用程序中可能遇到的几种内存泄漏,并介绍一些用于解决内存泄漏的技术。
理解内存使用
理解一个富客户机(Rich Client Platform(RCP))平台应用程序的完整内存使用会是一项脑力劳动。操作系统(OS)会指出应用程序耗费了多少内存,Java™ 平台会指出您已经耗费了多少堆。操作系统汇报的内存使用情况总是高于可用堆大小。不幸的是,有时操作系统所报告的数目会远远 大于堆大小。对于堆分析的一个挑战就是判断这片 “黑暗空间” 中藏匿着什么。
一般而言:进程使用的内存 = Java 堆 + 已编译的本地代码 + 字节码 + 其他 / 本地
很不幸,JVMS 根据其发行版本和供应商的不同,指示出的堆大小也不同。我所运行的一个 Java 应用程序就可以给出一些例子:Sun 1.6 JDK 报告堆大小为 32.7MB ,而操作系统报告为 48.6MB 私有字节,有 16MB 未作说明。总的来说这还算不错。已编译代码和字节码是这 16MB 的一部分。用 IBM® 1.5 JDK 运行同一应用程序,堆加上类加载器和已编译代码总共是 39MB,而 OS 报告的大小为 45.8MB。
一般而言,您可以把问题简化为只关注 Java 堆。这对绝大多数 Java 应用程序而言已经足够了,而且也可以让应用程序做到最大程度的改进。如果还不够,那么您应该使用操作系统工具检查未被 Java 堆覆盖的本地内存。
差异分析(Differential analysis)
处理内存使用问题中最为行之有效的一种手段是关注对象数目。举例而言,如果要在某个邮件应用程序中显示 50 条邮件消息,那么需要多少个 MailMessage 类的实例? 50,对吗?那么邮件详情或其他邮件域对象呢?如果切换了文件夹,显示新的 50 条邮件消息,又将发生什么情况呢?您会拥有多少个对象:50 还是 100?
一旦开始进行此类分析,您就会对实例数目大大超过期望数目这一常见情形感到惊讶。注意:在您收集堆转储之前,确保已经发生了垃圾收集行为,因为您不会想去考虑那些已经死亡的对象。一般情况下,我会在捕获堆转储前做一个 System.gc() 操作。
我并不想去描述司空见惯的一般性堆分析(请参阅 参考资料)。 相反,我将介绍差异分析(differential analysis),这是用于发现应用程序中内存泄漏的技术。
它的基本思想很简单:
得到一个堆转储。
在应用程序中多次做某件事(假设做 10 次)。
得到另一个堆转储。
比较两个堆转储中应用程序对象的数目。
这样就可以构建所需应用程序对象集合。随着泄漏的发现和处理,将泄漏到脚本的类添加到一个列表。这样一来,不长时间就可以构建经常检查的应用程序对象集合。
单元测试
我所用的另一个技术是写单元测试,解析堆转储并对期望的域对象实例数目做断言。比如说,您可以启动应用程序,运行一个场景,得到一个对转储,接着做断言。下面是一个例子:在邮件应用程序中发现一个内存泄漏,当该泄漏被处理后,我希望确定在以后的代码改变中不会再发生该问题,于是为此构建了一个单元测试。这是一个资源使用 单元测试,如清单 1 所示:
清单 1. JUnit 测试用例,解析堆转储
文章来源于领测软件测试网 https://www.ltesting.net/