首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

使用 IBM Support Assistant 找出在企业应用程序中确定和解决性能问题的模式(1)

使用 IBM Support Assistant 找出在企业应用程序中确定和解决性能问题的模式(1)

案例研究 1:基础架构代码中的内存泄漏我们的第一个案例研究涉及一个开源基准,其名称为 。RUBiS 模拟一个拍卖网站。它配备了自己的工作负载驱动程序,并且有一个 Web 层连接到一个数据库层。在有关性能评估的研究论文中已大量引用它(参见 )。
当使用 RUBiS 作为我们进行其他研究的一个基准时,我们注意到,RUBiS 在实验结束时会停止响应,并且必须重新启动才可以进行下一个实验。我们正在使用 servlet 版本的 RUBiS 和 MySQL JDBC 驱动程序。我们使用  对 100 个客户端的恒定负载分析了该应用程序详细的 GC 日志。通过 Java 命令行或通过对大多数商用应用服务器的管理用户界面,可以很容易地启用详细的 GC 日志。结果显示,即使在恒定负载(参见图 1)的情况下,所使用的堆也始终在不断增长。这似乎是一个典型的内存泄漏问题。然而,我们发现,该内存泄漏的根本原因并没有像大部分经典的内存泄漏那么明显。
图 1. GCMV:在恒定负载的情况下,RUBiS 堆的使用量在不断增长对于内存泄漏分析,最好先了解一些基本定义:
  • Shallow size,一个对象的 Shallow 大小是指用于存储对象本身所分配的内存量,没有考虑到所引用的对象。
  • Retained size,一个对象的 Retained 大小是其 Shallow 大小加上只可以从该对象直接或间接访问的对象的 Shallow 大小。
  • Retained set 为 X,如果 X 是被收集的垃圾,那么 Retained 集所代表的一组对象就将是被收集的垃圾。
  • Dominator tree 是一个转换,将循环的对象图转换成 Keep-Alive 树,树中的每个节点直接负责保持其子节点的活动状态。
我们首先使用  在几分钟内在恒定负载条件下提取此应用程序的两个堆转储,并比较它们。在大多数商用应用服务器中都可以很容易地启用堆转储。快速浏览一下在这两个堆转储中的对象计数,可以使我们注意到一组已经比原始数量和大小增长了近两倍的对象。这些对象大多是原始类型,但增长得最多的那些与 JDBC 中的 PreparedStatement(这种对象的数量在第一个和第二个堆转储之间几乎翻了一番)调用相关。图 2 显示了增长最多的对象的快速分解。在大多数内存泄漏中,有一些容器对象的大小增长最多,而另一些对象类型则是计数的增长最多。
图 2. MAT: 两个堆转储之间增长最多的 RUBiS 对象从图 2 中的快照可以看到,PreparedStatement 类型的对象计数增长得最多。然而,我们仍然需要找到在代码中的哪个部分使此计数增加(换句话说,此内存泄漏位于代码中的哪个位置)。有一个方法可以做到这一点,在 MAT 中查看支配树(参见图 3)。您可以清楚地看到两个 servlet(ViewBidHistory 和 BrowseCategories)在所使用的堆中占了 50% 以上,这两个类很可能包含了内存泄漏的代码。
图 3. MAT: 支配树中有两个 servlet 在所使用的堆中所占比例超过 50%因为我们手上有该应用程序的源代码,我们在两个 servlet(ViewBidHistory 和 BrowseCategories)中搜查 PreparedStatement 对象的使用,我们发现了图 4 所示的模式。还记得,一个 PreparedStatement 对象代表一个预编译的 SQL 语句,通过指定的赋值方法​​可以设置每次的 IN 参数。通过这种方式,它可以多次重用于高效执行 SQL 语句。然而,RUBiS 代码在一个循环内重复创建了许多 “临时的” PreparedStatement 对象,但只关闭所创建的最后一个实例。 说明,关闭 JDBC 驱动程序应该关闭与一个连接相关联的所有 PreparedStatement(此时连接已关闭)。在这种情况下,JDBC 驱动程序没有在关闭连接时关闭 PreparedStatement 对象,并保留了引用,这导致那些对象没有被作为垃圾收集,所以它们的数量不断增加,从而导致内存泄漏。
有两种方法可以修复这种泄漏:将语句关闭方法移至循环内,或将语句创建移到循环上方。两种方法都将修复内存泄漏,但后者可能会提供更好的性能。我们发现,RUBiS 中的 22 个 servlet 中有 7 个由于这种使用模式而在各个地方发生内存泄漏。图 5 显示了在修复内存泄漏后,GCMV 中所产生的恒定负载的​​堆使用图。请注意,所使用的堆几乎如预期般保持不变。
图 4. 内存泄漏根本原因的 RUBiS 代码本案例研究强调了在基础架构代码中的内存泄漏场景,其中发生泄漏是因为使用不当以及错误的的驱动程序实现共同造成的。此外,根源原来是这么一回事,人们通常不认为这是泄露的常见原因。
图 5. GCMV: 修复内存泄漏后,恒定负载情况下的恒定 RUBiS 堆使用
返回列表