内容


权威支持

利用 Memory Dump Diagnostic for Java (MDD4J) 分析内存管理问题

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 权威支持

敬请期待该系列的后续内容。

此内容是该系列的一部分:权威支持

敬请期待该系列的后续内容。

每一期的 “权威支持” 中都会介绍为 WebSphere® 产品提供的 IBM® 技术支持的资源、工具和其他元素,以及可进一步增强 IBM 支持体验的技术和新理念。

开始之前

与往常一样,我们仍然从 WebSphere 社区普遍感兴趣的一些新内容开始:

继续关注 各种与支持有关的网站,以及本专栏,了解关于我们接触到的其他工具的新闻。

现在,让我们回到主题……

简介

这一部分是 Java 内存转储诊断 (MDD4J) 故障排除工具的简介,这种工具可帮助您分析 Java 堆,从而诊断内存占用问题。MDD4J 的分析结果在报告中提供,此报告汇总了应用程序使用 Java 堆的情况。

共有三种适合使用 MDD4J 提供帮助的场景:

  • 内存泄漏:如果应用程序出现 java.lang.OutOfMemoryError 异常,或者详细的垃圾收集数据显示内存消耗逐渐增加,那么 MDD4J 可以指出造成此类增加的数据结构以及这些数据结构内的组件。
  • 过度的内存消耗:如果一个应用程序未能很好地伸缩,或者所占用的内存比实现其所需任务占用的必要内存多,MDD4J 可为您显示占用过多内存的数据结构的高级说明。MDD4J 报告可为您显示集合的实现和其他类型的数据建模是怎样造成过度内存消耗的。
  • 回归测试:MDD4J 提供的各种分析和数据结构视图可在修订 bug、添加或删除新特性时,帮助您跟踪内存消耗的更改。

MDD4J 可通过 IBM Support Assistant 获得。由于内存堆分析要占用大量处理器和 I/O 资源,您还可以在 IBM Support Assistant 工作台之外运行它。在服务器类机器上执行内存堆分析——特别是 64 位机器,允许您处理无限大小的内存堆,并为您的工作站保留大量资源,使您能够执行其他任务。

MDD4J 设计目标是提供足够的信息,帮助您识别问题,而不产生信息过载的问题。它并未提供深入、低级的专家功能,即手动检查堆内的整个对象图。如需获得深入的堆转储分析,可以使用 IBM Monitoring and Diagnostic Tools for Java – Memory Analyzer。

什么是堆转储?

堆转储也称为堆快照,就是内存中对象之间的参照转储,以及关于这些对象的信息。堆转储不包含变量名、值或源代码等信息。

在执行堆分析之前,需要一个堆转储。有三种方法可生成堆转储:

  • 自动:JVM 通常会在用尽内容和崩溃时生成堆转储。
  • 手动:可以向 JVM 发送一个信号,要求 JVM 生成堆转储。(MDD4J 文档具有关于如何在各种平台上生成堆转储的信息)。
  • 编程:IBM SDK for Java 包含一个 com.ibm.jvm.Dump 类。使用其静态方法可以在您的应用程序运行的过程中生成堆转储。

如何分析堆转储

MDD4J 为堆转储文件使用不同的术语,以其获得的时间为依据:

  • 基准堆转储

    尽管 JVM 在崩溃时生成堆转储,但您可以将其与在应用程序启动后获取的堆转储相比较,进一步了解内存是如何使用的。这个在应用程序生命周期的早期获取的堆转储称为基准堆转储,可在执行比较分析时使用。

  • 主堆转储

    主堆转储是在问题发生时获取的转储,如用尽内存,或过度的堆占用。此堆转储可在 JVM 用尽内存或崩溃时自动生成。也可通过编程或向 JVM 发送信号来手动生成。可以在主堆转储上执行单一或对比性堆转储分析。

    MDD4J 允许执行两种不同类型的堆转储分析,具体取决于可用的堆转储数量:

    • 如其名称所示,单一堆转储分析是对一个堆转储执行的,也就是主堆转储。它为您展示关于堆的所有信息,包括可能造成泄漏的和重要的数据结构。
    • 比较性堆转储分析使用两个堆转储,也就是基准和主堆转储,并比较其差异。

在分析过程中,MDD4J 尝试找到可能性最高的泄漏疑点。之所以称为疑点,是因为没有方法能够准确识别过高的堆占用的原因。分析识别出泄漏疑点,并不表示确实存在泄漏。对于缓存实现来说,这一点尤为明显,因为那与典型的内存泄漏极为相似。

启动和理解 MDD4J

为了充分理解如何使用 MDD4J,您需要了解用于描述内存泄漏的几个术语的含义。图 1 通过名为 MyClass 的示例展示了这些术语,此示例具有一个 HashSet,包含字符串对象。

图 1. 内存泄漏术语
图 1. 内存泄漏术语
图 1. 内存泄漏术语
  • 泄漏根:包含导致泄漏容器的对象链的引用的对象。如果在所有者链中未发现任何类对象,则此术语表示可找到泄漏容器的内存转储中的根对象。
  • 泄漏容器:唯一拥有所有泄漏对象的对象。在本例中是 HashMap。
  • 泄漏单元:存在多个实例的数据结构内的对象。在本例中是 HashMap 内的 HashMap$Entry 对象。
  • 所有者链:从泄漏根对象开始到泄漏容器对象的对象链。
  • 内容:泄漏根下为堆消耗的大部分负责的实际数据。

另外还有一些未在图 1 中出现的信息,但与内存泄漏相关:

  • 区域大小:给定对象可接触的所有对象的总大小。在本例中,就是影响泄露根的区域大小的所有对象。
  • 区域大小落差:一个对象的区域大小和该对象的子对象的区域大小之间的差别。此差别与实例计数的差别是发现泄漏疑点的关键。

MDD4J 的启动与 IBM Support Assistant workbench 中安装的其他工具相同。图 2 显示了 workbench 中的 MDD4J 在启动并完成分析后的状态。在分析完成后,您看到的第一个视图就是 Analysis Summary。(如果希望将 MDD4J 从 workbench 导出到其他机器上,将有一个批处理文件或 shell 脚本可用于运行此工具)。

在下面的几节中,您将了解分析结果在 MDD4J 中的显示方式。所显示的信息针对主转储。

评估分析结果

Analysis Summary 是您在分析完成后看到的第一个视图。它显示了分析结果的信息汇总,并提供了堆内容和堆中主要组成部分的快速概览。在这个窗格中,可以确定在堆大小中占最大比例的组件和类型。

图 2. IBM Support Assistant workbench 中的 MDD4J
图 2. IBM Support Assistant workbench 中的 MDD4J
图 2. IBM Support Assistant workbench 中的 MDD4J

Analysis Summary 具有三个部分:

  • Basic Heap Information(如图 3 所示)显示关于堆和堆转储文件的信息:堆大小、对象数量、对象类型数量、堆转储文件名称和堆转储文件的获取日期。
    图 3. Basic Heap Information
    图 3. Basic Heap Information
    图 3. Basic Heap Information
  • High-level Summary of the Heap Contents(如图 4 所示)显示了两个图表,显示在堆大小中占比例最大的堆转储中的组件和类型。
    • Predefined Components 图显示了与 Java 和 WebSphere 应用程序中的常用组件相比,各组对象在堆大小中所占的相对比例。
    • Type Breakdown 图显示了最常用的 Java 类在堆大小中所占的相对比例。
    图 4. High-level Summary of the Heap Contents
    图 4. High-level Summary of the Heap Contents
    图 4. High-level Summary of the Heap Contents

在 Analysis Summary 视图中,还可访问单一文件的 HTML 报告(如图 2 所示),其中提供了分析结果的汇总。报告包含 MDD4J 中各种视图提供的所有信息,但未显示图形。可利用此报告与您的同事快速共享分析结果。

还可以下载一部分 MDD4J 用户界面(Data Structures 选项卡,本文稍后将加以介绍),将其作为 Yeti Report 独立报告(如图 2 所示),并保存所得到的 .zip 压缩文件。将压缩文件的内容解压到空目录中,在 Web 浏览器中打开 index.html 文件。关于 Yeti,请参见 认识大型堆

区域大小泄漏疑点

此视图是识别内存泄漏的起点。分析几乎总是会发现某些疑点,但并非全部疑点都是真正的内存泄漏。泄漏疑点按区域大小排序,最重要的泄漏疑点显示在最顶端。

在分析内存不足的情况时,这是信息的主要来源。如果存在内存消耗过度的现象,此视图可提供有用的信息。而 Data Structures 视图可以提供更多细节。

此外有两个子视图,均显示泄漏疑点,但针对不同级别(如图 5 和图 6 所示)。在大多数情况下,顶级泄漏疑点在两个视图中是相同的——但若顶级泄露疑点不同,就有必要审查两个视图中的疑点:

  • 聚合数据结构级别(如图 5 所示)针对相同所有者链内相同类型的实例汇总。泄漏疑点组织为表,每个表表示一个泄漏根。泄漏根的名称列于表上方,显示为一个超链接,链接到内容示意图。内容示意图就是泄漏根下所有者链的图形表示。
  • 对象/类级别(如图 6 所示)针对堆的原始参照图中的时机实例。在大多数情况下,所显示的信息应与聚合数据结构级别视图相同。

聚合数据结构泄漏疑点

有多个表表示泄漏根下参考树中发现的疑点,每个表针对一个泄漏根。泄漏根列在表上方(如图 5 所示),是链接的形式,链接到泄漏根所有者链的图形表示,这也称为内容示意图。这个图形表示可帮助您了解表中所示的泄漏根、泄漏容器和泄漏单元之间的关系。此表显示了一对泄漏容器和泄漏单元,带有泄漏单元的实例数量及其总大小。

例如,图 5 显示,EvaluateServlet 类的一个实例是泄漏容器,ArrayList 类的一个实例是第一行的泄漏类型。通常,您会看到表的一行中的泄漏单元是同一个表中另一行内的泄漏容器。这也就是说,存在嵌套的容器类型。

例如,图 5 中的第 3 行列举了 HashMapi 实例包含的 Analyzer 类的一个实例,第四行显示同一个 HashMap 包含 1000 个 Analyzer$Chunk 类实例。

图 5. 聚合数据级泄漏疑点
图 5. 聚合数据级泄漏疑点
图 5. 聚合数据级泄漏疑点

应自上而下地阅读此表来解释信息(如图 5 所示)。在某些情况下,如本例中的iqngkuang,可能存在多个不同的泄漏。下方的每个数字都对应于表中的相应行。

  1. 包含 ArrayList 的一个实例(大小为 78MB)的 EvaluateServlet 实例。
  2. 此 ArrayList 包含 Analyzer 的一个实例。
  3. 此 Analyzer 包含 Hashtable 的一个实例。
  4. 此 Hashtable 包含 629 个 Chunk 实例,这是 Analyzer 的内部类。
  5. 每个 Chunk 实例包含一个 ArrayList。
  6. 1000 个 Chunk 实例中的所有 ArrayLists 包含 500,000 个 String 实例,总计大小为 76 MB。
  7. 下一个 Chunk 实例包含 Hashtable 实例(大小为 49 MB)。
  8. 此 Hashtable 包含 629 个 Chunk 实例。
  9. 每个 Chunk 实例包含一个 ArrayList。
  10. 1000 个 Chunk 实例中的所有 ArrayLists 包含 314,500 个 String 实例,总计大小为 48 MB。

对象/类泄漏疑点

这个视图比聚合数据级泄漏疑点列表更加简单。对象/类泄漏疑点表(如图 6 所示)显示了泄漏根及其泄漏容器的列表,以及容器的总大小和所有泄漏单元的区域大小落差。如前所述,此列表应包含与聚合数据结构级别表(如图 5 所示)相同的顶级泄漏疑点。

图 6. 对象/类泄漏疑点
图 6. 对象/类泄漏疑点
图 6. 对象/类泄漏疑点

图 6 显示了与聚合数据结构泄漏疑点视图(如图 5 所示)中的前两个泄漏疑点相同的一点。总大小的差别是由于聚合数据结构级别视图使用汇总实例计数,对象/类泄漏疑点视图使用实际事例和大小计数。

使用泄漏疑点视图

为了阅读两个泄漏疑点视图中的信息,首先要从第一行开始阅读表格。由于识别漏洞极为复杂,因而应注意列表中的最后一个疑点,若其泄漏容器大小低于堆大小的 5-10%(如图 2 的 Analysis Summary 视图所示),则表示这根本不是泄漏。此类微不足道的泄漏应予以忽略。

顶级泄漏疑点十分重要,您应点击表上方的链接,查看其内容示意图。随后查看表中列举的泄漏疑点,识别哪些类只是容器,哪些类是泄漏类。为此,您需要了解为其生成堆转储的应用程序的结构或实现。请牢记,列为泄漏类的类本身可能并不是导致泄漏的原因。更有可能是其他用于创建类实例的代码导致了过度的内存占用。

识别出泄漏类后,您要检查 Data Structures 视图,确认泄漏根或泄漏单元占用了多少堆。如果 Data Structures 视图中未列出任一项内容,则泄漏并非真正的泄漏,此时应审查 Data Structures 视图中提供的占用堆比例最大的因素。(Data Structures 视图将在稍后详细介绍)。

对象表

Object Tables 视图包含两个子视图,列举了堆中的所有类和各类的实例。利用 Object Tables 选项卡中的视图可获得更多信息,包括堆中指定对象和类的数量,或确认泄漏疑点的大小。

两个表中的数据均可按列排序,只需点击列标题即可。再次点击列即可切换排序的次序。图 7 显示了按实例数量降序排列的表。

图 7. 按实例数量排序的对象表
图 7. 按实例数量排序的对象表
图 7. 按实例数量排序的对象表

数据结构

Data Structures 视图(如图 8 所示)提供了关于应用程序数据结构的内部结构的更多细节。包含在此视图内的显示基于由 IBM Research 团队开发的 Yeti 技术。

图 8. Data Structures 视图
图 8. Data Structures 视图
图 8. Data Structures 视图

此视图中显示的信息可帮助您打造和连接堆转储中的数据结构。Yeti 可视化显示自动将对象实例组织为更大的内聚单元。这使您不必手动浏览独立对象和引用,并为您展示构成数据结构的更高级视图。其中的显示可帮助您:

  • 识别 Java 堆中的最大数据结构。
  • 跟踪这些数绝结构的大小变更。
  • 快速了解数据结构的内部结构。
  • 诊断造成内存膨胀的应用程序中的数据建模问题。
  • 根据影响软件组件内存占用情况的软件组件筛选数据结果。

此视图的左侧包含选项卡,右侧包含信息框。共有四个选项卡:Big(如图 9 所示)、Growing(如图 10 所示)、Shrinking(如图 11 所示)和 Steady(如图 12 所示)。

图 9. 大数据结构
图 9. 大数据结构
图 9. 大数据结构
图 10. 增长的数据结构
图 10. 增长的数据结构
图 10. 增长的数据结构
图 11. 缩小的数据结构
图 11. 缩小的数据结构
图 11. 缩小的数据结构
图 12. 稳定的数据结构
图 12. 稳定的数据结构
图 12. 稳定的数据结构

分析一个堆转储时,仅会填充 Big 选项卡。在分析两个堆转储时,Growing、Shrinking 和 Steady 选项卡也将被填充已大小有变化的数据结构列表,或保持稳定但在堆转储的大小中占较大比例的数据结构列表。内容示意图将有差异的信息显示为红色。

Data Structures 视图右侧的信息框(如图 8 所示)显示了整个堆的信息或者选定数据结构的信息。共有两个选项卡:OverviewHealth Report,显示了整个堆转储(如图 13 所示)的信息或为 Big、Growing、Shrinking 或 Steady(如图 14 所示)选定的数据结构的信息。

图 13. 整个堆的概览
图 13. 整个堆的概览
图 13. 整个堆的概览
图 14. 一个数据结构的概览
图 14. 一个数据结构的概览
图 14. 一个数据结构的概览

图 15 和图 16 显示了 Health Report 选项卡上的图表。在本例中,它们显示了 EvaluateServlet 实例的健康状况。

图 15. EvaluateServlet 的健康状况
图 15. EvaluateServlet 的健康状况
图 15. EvaluateServlet 的健康状况
图 16. EvaluateServlet 的健康状况(续)
图 16. EvaluateServlet 的健康状况(续)
图 16. EvaluateServlet 的健康状况(续)

探究数据结构

您看到的第一个视图就是显示堆中成本最高的数据结构的柱形图。前接 “statics” 的名称表示在给定类静态字段根下的数据结构。紧接其后的指令说明如何探索数据结构,适用于所有选项卡中显示的数据结构。

在每个柱形的右侧(如图 17 所示)给出了数据结构的大小(有时会给出两个大小),柱形的长度就是通过该数据结构可获得的字节总数。通常,数据结构有重叠,一个数据结构的内容同时也是另一个数据结构的一部分。若出现此类共享,您将看到两个数字,第二个数字位于括号中。

图 17. 数据结构大小
图 17. 数据结构大小
图 17. 数据结构大小

共享显示通过其他数据结构可获得该数据结构的大小比例。换句话说,显示了与其他数据结构共享的数据结构。可以选择一个数据结构,更详细地查看其内容。若点击数据结构柱形图中的任意柱形,窗口右侧就会更新为显示选定结构内容的基本汇总。图 18 展示了在点击图 17 中所示的 EvaluateServlet 柱形时显示的汇总信息。

图 18. EvaluateServlet 汇总
图 18. EvaluateServlet 汇总
图 18. EvaluateServlet 汇总

如果不存在共享,选择列表就只有单独一个项目,表示整个数据结构。在与其他数据结构共享一个数据结构的各部分时,选择列表将包含多个项目,每个项目针对一个共享部分。每部分都是一个子数据结构,所有对象都是该部分的唯一部分。在部分列表中,您将看到构成此结构同时又作为其他结构的组成部分的部分(末尾处标有 “shared”)与仅是此结构的组成部分的部分(无前述后缀)之间的差别.

探索内容示意图

为进一步了解一个部分的内部结构,点击数据结构类型的标有下划线的名称,或点击数据结构连接,选择集合类型(如图 19 所示)。您将看到一个弹出窗口,显示了选定部分的内容示意图(如图 20 所示)。

图 19. 选择数据结构类型
图 19. 选择数据结构类型
图 19. 选择数据结构类型

内容示意图显示了构成数据结构的具体内容。。它显示了数据结构如何利用集合(如 HashMap 或 Vector)来设定数据结构的元素的结构。图 20 和图 21 都是内容示意图的例子。应按照阅读 UML 或实体关系(ER)图的方式阅读示意图,每个节点都表示一个集合基础设施(例如,标准库实现 HashMap 的方式)或者目标程序包含的实体(例如,映射中的 String 键)。在两种情况下,对于集合和内含实体来说,内容示意图都隐藏了实现细节,所显示的唯一数字就是该实体实现细节的总体大小。

图 20. EvaluateServlet 内容示意图
图 20. EvaluateServlet 内容示意图
图 20. EvaluateServlet 内容示意图

图 20 显示了 EvaluateServlet 内容示意图。某些节点并未显示类名,这是由于这些实例对根类实例的大小无显著影响。此内容示意图显示了 500,000 个 String 实例的分配方式。

图 21 显示了更为复杂的内容示意图示例。

图 21. 复杂的内容示意图
图 21. 复杂的内容示意图
图 21. 复杂的内容示意图

类型图

如果内容示意图中的一个节点看起来过大,您可向下访问到显示该实体实现细节的视图。为此,双击内容示意图中的任意节点,您将看到另外一个弹出窗口,显示选定实体的类型图。图 22 显示了 EvaluateServlet(如图 20 所示)下的所有链引用的 Hashtable 的类型图。类型图显示一个实体的类(数据类型)相互连接的方式,显示每种类型对该实体在选定数据结构的内容示意图中所占大小的影响。

图 22. Hashtable 类型图
图 22. Hashtable 类型图
图 22. Hashtable 类型图

字段布局视图

如果类型图中的一个节点看起来过大,您可双击它,向下访问到字段布局视图(如图 23 所示)。对于支持此数据的堆快照(目前仅有 HPROF),此视图将显示选定类的字段以及该类的继承层次结构对于其实例大小的影响。通常,开发人员会为类添加很多字段,而未意识到为类所添加的每个整型字段都会在运行时与一个较大的因数(实例数量)相乘。例如,开发人员往往会为缓存计算添加字段,或在构建阶段仅添加必要的字段,而在应用程序的长期使用过程中,这些都不是必要的。

图 23. 字段布局视图
图 23. 字段布局视图
图 23. 字段布局视图

在 IBM Support Assistant 之外运行 MDD4J

IBM Support Assistant 提供了一种便利的集中平台,您可利用此平台发现、安装、执行和获取多种问题解决工具的定期更新,因而也应该使用它来获取 MDD4J 的更新。然而,分析极大的堆转储可能会在较长的时间内占用一台标准工作站的大部分 CPU 和 I/O 资源。在这些情况下,在服务器类及其上执行分析更有益。您可在 MDD4J Help 中找到关于在 ISA 以外运行此工具的说明(如图 24 所示)。

图 24. 在 ISA 之外运行 MDD4J
图 24. 在 ISA 之外运行 MDD4J
图 24. 在 ISA 之外运行 MDD4J

结束语

现在,您已经了解了 Java 内存转储诊断(MDD4J)工具在发现内存泄漏和其他类型的内存使用问题方面的强大力量,下一次,在怀疑应用程序未能合理管理 Java 堆时,您应可以轻松解决问题。务必探索 IBM Support Assistant 中的其他工具,进一步加强您的故障排除武器。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere, Java technology
ArticleID=449612
ArticleTitle=权威支持: 利用 Memory Dump Diagnostic for Java (MDD4J) 分析内存管理问题
publish-date=11252009