admin 管理员组

文章数量: 1184232


2024年3月1日发(作者:伸缩型钢z)

Memory Analyzer 使用文档

Memory Analyzer Tools

使用说明

zhyea robin

1

致一思维翻译整理

Memory Analyzer 使用文档

简介

Eclipse Memory Analyzer是一个功能丰富且轻量的Java堆内存分析工具,可以用来辅助发现内存泄漏减少内存占用。

使用Memory Analyzer来分析生产环境的Java堆转储文件,可以从数以百万计的对象中快速计算出对象的Retained Size,查看是谁在阻止垃圾回收,并自动生成一个Leak Suspect(内存泄露可疑点)报表。

Memory Analyzer有两种使用方式:一种是下载独立版本的MAT,一种是使用嵌入到Eclipse中的MAT插件。我这里是用的eclipse插件。如果平时用的是其他IDE,可以尝试使用独立版MAT。

概念

1. Heap Dump

Heap Dump是一个Java进程在某个时间点上的内存快照。Heap Dump是有着多种格式的。不过总体上Heap Dump在触发快照的时候都保存了java对象和类的信息。通常在写Heap Dump文件前会触发一次FullGC,所以Heap Dump文件中保存的是FullGC后留下的对象信息。

Memory Analyzer可以用来处理HPROF二进制Heap Dump文件、IBM系统dump文件(经过处理后)、以及来自各个平台上的 IBM portable Heap Dumps (PHD)文件。

一般在Heap Dump文件中可以获取到(这仍然取决于Heap Dump文件的类型)如下信息:

 对象信息:类、成员变量、直接量以及引用值;

 类信息:类加载器、名称、超类、静态成员;

 Garbage Collections Roots:JVM可达的对象;

 线程栈以及本地变量:获取快照时的线程栈信息,以及局部变量的详细信息。

Heap Dump文件中并不包含内存分配信息,所以通常无法通过Heap Dump文件解决是谁以及在哪里创建了哪些对象这样的问题。

2

致一思维翻译整理

Memory Analyzer 使用文档

2. Shallow or Retained Heap

Shallow Heap表示一个对象消费的内存的总量。对象的每个引用变量会占用32或64bit(取决于操作系统),每个Integer需要占用4byte,每个Long需要占用8byte,诸如此类的其他信息可以自行查询。Shallow

heap的值可能是经过了调整的(比如对齐到8,具体取决于Heap Dump文件的格式),以便更好地模拟虚拟机的真实消费情况。

对象X的Retained Set指的是一旦X被垃圾回收后也会随之被GC回收掉的对象的集合。

对象X的retained heap指的是X的retained set中所有对象的shallow heap之和,或者说是因为对象X而保持alive的内存的大小。

通常来说,shallow heap就是对象自身在堆内存中的大小,而同一个对象的retained heap指的是该对象被垃圾回收后释放的堆内存的大小。

一组leading对象的retained set(如一个特定类的全部对象、一个特定类加载器加载的所有类的全部对象、又或者一串任意的对象)在leading对象集合中的对象全部不可达时被释放掉。Leading对象集的retained

set包括这些对象本身和其他的只能通过这些对象访问到的对象。而leading对象集合的retained size指的就是retained set中的全部对象的堆内存之和。如下图:

Minimum Retained Size提供了一种很好的估算retained size的方案。这种方案的计算速度远比获取精确的retained size快得多。因为这种计算方式只依赖于要查看的集合中的对象的数量,而非Heap Dump中对象的数量。

3

致一思维翻译整理

Memory Analyzer 使用文档

3. Dominator Tree

Memory Analyzer提供了一个dominator tree(支配树)的概念来描述对象关系图。将对象引用图转为dominator tree可以使你很容易地找到占用内存最大的一块以及对象之间的依赖关系。如下是对dominator

tree的一些正式定义:

 如果说对象X支配(dominate)了对象Y,那么在对象关系图中,从起始节点(或者说是根节点)到Y的每一条路径都必须经过X。

 对象Y的直接支配者(immediate dominator)X是距离对象Y最近的一个支配者。

Dominator Tree基于对象关系图构建。在Dominator Tree中,每个对象都是它的子节点的直接支配者,所以对象间的依赖关系很容易确认。

Dominator tree有如下几个重要的特征:

 X的子树中的对象(也就是由x支配的对象)代表了X的retained set;

 如果x是y的直接支配者,那么x的直接支配者同样支配着y,以此类推;

 Dominator tree中的边并不直接对应着对象引用图中的对象引用关系。

下图是一张对象引用关系图与dominator tree的对照图:

4. Garbage Collection Roots

一个garbage collection root(简称GCRoot)就是一个可以从堆内存以外访问的对象。以下原因使一个对象成为GCRoot:

4

致一思维翻译整理

Memory Analyzer 使用文档

System Class

由引导类加载器(bootstrap classloader)或系统类加载器(system classloader)加载的类。比如由加载的全部类,如.*。

JNI Local(局部JNI对象)

native代码编写的局部变量,比如用户自定义的JNI代码或者JVM内部代码。

JNI Global(全局JNI对象)

native代码编写的全局变量,比如用户自定义的JNI代码或者JVM内部代码。

Thread Block

当前活跃的thread block中引用的对象。

Thread

已经启动的,仍未终止的线程。

Busy Monitor(活跃的监控器)

所有调用了wait()、notify()或者进入同步的对象或类(静态方法指的是类,非静态方法时是对象)。举例说如调用了synchronized(Object)或者进入了一个同步的方法。

Java Local

局部变量。指的是仍在线程栈中的方法的输入参数或者创建的局部对象变量。

5

致一思维翻译整理

Memory Analyzer 使用文档

Native Stack

native代码中的输入输出参数,如用户自定义的JNI代码,或者JVM内置代码。通常是在这样的情况下:一个方法中有native代码参与,作为这个方法的参数的对象就成了GCRoot。比如说用在文件操作或者网络传输中的IO方法的参数、或者用在反射中的参数。

Finalizable

进入等待队列,等待自己的Finalizer执行的对象。

Unfinalized

一个有finalize方法的对象,但是尚未被finalize也没有进入等待执行finalize的队列时。

Unreachable

其他GCRoot皆不可达的对象,但是也不会被MAT标记为GCRoot。这种对象不会被纳入分析的范围。

Java Stack Frame(Java栈帧)

Stack frame(堆栈帧)是一个为函数保留的区域,用来存储关于参数、局部变量和返回地址的信息。只有在解析dump文件时将之视为一个对象来处理,才会认为其是一个GCRoot。

Unknown

未知类型的root对象。一些dump文件,如IBM Portable Heap Dump文件,其中并不包含root信息。MAT在解析这一类文件时,如果发现某些对象不存在任何引用信息或者对任何GCRoot均不可达,那么这些对象就会被认为是未知类型的GCRoot。这样可以保证GCRoot持有dump中的所有对象信息。

5. Incomming & Outgoing Reference

简言之,Incomming Reference指的是引用当前对象的外部对象;Outgoing Reference指的是当前对象引6

致一思维翻译整理

Memory Analyzer 使用文档

用的外部对象。

对象的incomming reference保证对象处于alive从而免于被垃圾回收掉。

Outgoing reference则展示了对象的具体内容,有助于我们发现对象的用处。

基础教程

这里的基础教程可以用来作为熟悉Eclipse Memory Ananlyzer的一个出发点。

第一步:获取Heap Dump文件

Memory Analyzer主要基于Heap Dump文件工作。Heap Dump文件中包含了在某个指定时间点上所有alive状态的java对象的信息。现在所有Java虚拟机都有写Heap Dump文件的功能,但是具体的步骤随着提供商、版本以及操作系统的不同而有些差别。

在这个教程中会尝试使用windows下的java8和jconsole来下载Heap Dump文件。使用java8启动一个应用,打开/bin/,在jconsole中选择正在运行的应用

(在这个例子中选择的是eclipse,虽然进程名称为空,但是通过重启eclipse可以确认)。

建立连接后,选择页签MBean,执行ment. HotSpotDiagnostic下的操作dumpHeap。7

致一思维翻译整理

Memory Analyzer 使用文档

第一个参数p0是要获取的dump文件的完整路径名,记得文件要以.hprof作为扩展名(要在Memory

AnalysisPerspective下打开扩展名必须是这个)。如果我们只想获取live的对象,第二个参数p1需要保持为true。

建议将导出的dump文件保存到一个独立的文件夹,在接下来的分析中会通过这个文件创建很多图表文件。

第二步:Overview(概览)

在eclipse上通过File –> OpenFile来打开dump文件,之后会直接进入Overview页。当然也可以打开Memory AnalysisPerspective,这个通常需要手动调整下:

8

致一思维翻译整理

Memory Analyzer 使用文档

之后在弹出框中选择Memory Analysis即可:

在MemoryAnalysisPerspective下还可以使用File -> Open Heap Dump打开dump文件。在Memory

AnalysisPerspective下Overview是这样子的:

9

致一思维翻译整理

Memory Analyzer 使用文档

在右侧窗口上方的位置可以看到heapDump的size,以及类、对象和类加载器的数量。

右侧窗口中最醒目的饼图直观地显示了dump中最大的几个对象。鼠标光标划过饼图中代表某个对象的区块时可以在左侧Inspector窗口中看到对象的细节,在区块上点击鼠标左键可以通过菜单项钻取到关于其对应的对象更多的细节。

第三步:The Histogram(直方图)

在工具栏中选择histogram可以查看每个类的实例的数量,以及shallow size和retained size。

Memory Analyzer默认展示了每个对象的retained size。然而同一组对象(在这里指的是同一个类的所有10

致一思维翻译整理

Memory Analyzer 使用文档

实例)的retained size仍然需要计算。

要估算所有行的retained size,可以使用工具栏中的。此外,也可以单独选择几行,使用右键菜单中的对应选项来进行计算(如果retained heap已经有值的话计算将不会起到作用)。

在Histogram中使用右键菜单可以深入钻取选定行所代表的对象的信息。比如说,可以使用右键菜单查看对象的incomming reference和Outgoing reference,可以按对象值的属性进行分组,也可按集合的size对之进行分组…

Memory Analyzer的一个强大之处就在于使用者可以从多个角度钻取对象信息进行分析。所以在需要的时候,可以尝试对象进行切片和钻取。

查看对象的Outgoing reference:

另一个重要的功能是按classloader、package或者superclass对histograme进行分组:

11

致一思维翻译整理

Memory Analyzer 使用文档

很多不错的应用都采用了由不同的类加载器加载不同组件的方案。Memory Analyzer会为每个Classloader绑定一个有意义的标签,如果门使用的是OSGI bundles,那么这个标签就是bundle id。通过这个,我们可以更容易将Heap Dump做更细化的拆分。

按package对histogram分组可以有层次的分析java的package:

12

致一思维翻译整理

Memory Analyzer 使用文档

按superclass对histogram进行分组提供了一种更简单的方式来做诸如找到ctMap所有子类这样的工作:

13

致一思维翻译整理

Memory Analyzer 使用文档

第四步:Dominator Tree

Dominator Tree展示了Heap Dump中最大的几个对象。如果dominator tree中对象的父节点被移除的话那么,那么相应对象及其后代节点也面临被回收的状态。

如果想探究一个对象持有了哪些对象并使之处于alive,Dominator Tree会是个很有用的工具。此外还可以在Dominator Tree上按照classloader或package进行分组,从而简化分析的过程。

14

致一思维翻译整理

Memory Analyzer 使用文档

第五步:到GC Roots的路径

Garbage Collections Roots(GC Roots)是被虚拟机本身直接持有的对象。其中包括当前正在运行的线程对象、正在调用栈中的对象、以及被系统类加载器加载的类的信息。

从一个对象到一个GCRoot的(倒序)引用链——即到GCRoot的路径——说明了为什么对象不会被回收掉。通过引用路径可以帮助发现解决大部分Java中的内存泄露问题:内存泄漏的发生的原因就是即使程序逻辑中某个对象不会再被已经访问了,但是该对象的引用仍然存在。

15

致一思维翻译整理

Memory Analyzer 使用文档

打开path2gc窗口后,最先展示的就是到GC Root的最短路径。

第六步:Leak Report

Memory Analyzer会分析Heap Dump文件并检测内存泄漏的可能,比如一个或一组异常大的对象。

16

致一思维翻译整理

Memory Analyzer 使用文档

17

致一思维翻译整理

Memory Analyzer 使用文档

任务

1. 获取Heap Dump文件

HPROF二进制Heap Dump文件

获取HPROF文件有三种方式。

1. 通过OutOfMemoryError获取Heap Dump

通过设置如下的JVM参数,可以在发生OutOfMemoryError后获取到一份HPROF二进制Heap Dump文件:

-XX:+HeapDumpOnOutOfMemoryError

生成的文件会直接写入到工作目录。

这个方案适用于如下版本的虚拟机:Sun JVM (1.4.2_12 or higher and 1.5.0_07 or higher), HP-UX JVM

(1.4.2_11 or higher) and SAP JVM (since 1.5.0)。

2. 主动触发一次Heap Dump

也可以为虚拟机设置下面的参数,这样就可以在需要的时候按下CTRL+BREAK组合键随时获取一份Heap

Dump文件。

-XX:+HeapDumpOnCtrlBreak

3. 使用HPROF agent

使用agent可以在程序执行结束时或者收到SIGQUIT信号时生成dump文件。使用agent也是要配置虚拟机参数的:

-agentlib:hprof=heap=dump,format=b

此外也可以选择下面的方法获取dump文件:

 使用jmap:jmap -dump:format=b,file=<>

 使用jconsole,在上一节基础教程中有提过;

 使用Memory Analyzer,稍后会演示如何做。

18

致一思维翻译整理

Memory Analyzer 使用文档

从IBM虚拟机中获取system dump和Heap Dump

Memory Analyzer也可以从IBM的system dumps和Portable Heap Dump (PHD)文件中读取内存相关的信息。这只需要在Memory Analyzer中安装对IBM DTFJ特性支持的插件即可,要求Memory Analyzer的版本最低为0.8。如何安装IBM DTFJ特性支持请参考这篇文章:《IBM DTFJ feature installation instructions》。在成功安装后,再打开File > Open Heap Dump会增加如下选项:

 IBM DTFJ for 1.4.2 VMs

 IBM DTFJ for Portable Heap Dumps

 IBM SDK for Java (J9) Javadump

 IBM SDK for Java (J9) System dumps

要生成可供Memory Analyzer使用的dump文件,要求使用的IBM虚拟机最低版本是IBM JDK 1.4.2 SR12,

5.0 SR8a and 6.0 SR2 t。尽管之前的版本也能生成Memory Analyzer可用的dump文件,但是root信息不够准确。

1. IBM Java5.0和IBM Java6虚拟机的dump操作

对于IBM Java5.0和IBM Java6虚拟机来说,只需要添加如下的命令行参数即可:

-Xdump:system+heap+java:events=systhrow+user,filter=java/lang/OutOfMemoryError,request=exclusive+prepwalk+compact

命令参数前有一个“-”,请注意下。需要解释下命令行参数中几个主要信息:

dump类型:

 system:一个系统进程dump。将system dump文件加载进Memory Analyzer前需要先使用jextract处理一下。不要给处理后的文件添加一个.sdff扩展名,因为这只用于java 1.4.2的system dump文件;

 heap:一个Portable Heap Dump (PHD)文件,包含了所有类和对象的信息,但是没有线程的细节;

 javacore:一个可读文件,包含了类加载器信息,可以在用Memory Analyzer读取PHD文件时使用。

Events:

 systhrow:在系统发生异常时;

 user:用户使用CTRL + BREAK时。

Filter:

19

致一思维翻译整理

Memory Analyzer 使用文档

 java/lang/OutOfMemoryError:指的是系统抛出的异常的类型。

Request:

 exclusive:在生成dump文件时,停止一切对堆内存的修改;

 prepwalk:保证生成dump文件时堆内存足够安全;

 compact:压缩dump文件的大小。

2. IBM Java 1.4.2虚拟机的dump操作

在非z/OS系统上,需要先使用JExtract对系统dump文件进行处理,最终生成一个扩展名为.sdff的文件。在z/OS系统上,需要将dump文件以binary模式拷贝至Memory Analyzer的系统上,并修改扩展名为.dmp。

通过Memory Analyze获取Heap Dump文件

如果要获取dump文件的Java进程和Memory Analyzer在同一台机器上,可以直接使用Memory Analyzer获取dump文件。采用这种方式获取dump文件后会直接将之解析并在Memory Analyzer中打开(这个方式我太喜欢,因为会在用户目录下生成很多零碎的文件)。

获取Heap Dump是受虚拟机支持的一种特定的功能。Memory Analyzer提供了一些被称为Heap Dump

Provider的概念:比如支持Sun虚拟机(需要用到Sun JDK的jmap功能)的Provider或支持IBM虚拟机(也需要一个IBM JDK)的Provider。此外,Memory Analyzer也提供了对用户自定义的Heap Dump Provider插件的扩展支持。

要使用Memory Analyzer获取dump文件需要先打开Memory AnalysisPerspective,而后点击File -> Acquire

Heap Dump…菜单项。之后会打开如下窗口:

20

致一思维翻译整理

Memory Analyzer 使用文档

在上图中,根据具体的运行环境,预装的Heap Dump Provider按照默认设置展示了当前正在运行的java进程列表。一些Heap Dump Provider也允许(或者说需要)设置一些额外的参数(比如Heap Dump的类型),这个可以点击Configure按钮来设置。选择要dump的线程,点击Next按钮就可以生成dump文件了。

有的时候进程列表可能为空,这时有必要调整下Heap Dump Provider的设置了。点击Configure按钮,选择合适的provider并点击,然后找到要做调整的参数并做设置就好了。

一个Heap Dump文件中有多个内存快照

有的时候生成的一个dump文件中可能会有多重内存快照,比如,如果是使用如下参数生成的dump文件:

-agentlib:hprof=heap=dump,format=b

那么多次触发Heap Dump操作就有可能导致所有的Heap Dump信息都写到一个文件里。再比如,IBM的z/OS上的系统dump有可能包含来自多个地址空间或者进程的数据,因此dump文件中也就有可能会有来自多个java runtimes的Heap Dump快照。

21

致一思维翻译整理

Memory Analyzer 使用文档

Memory Analyzer1.2及之前的版本会默认使用第一个Heap Dump快照——除非是在环境变量中或者MAT DTFJ configuration设置中选择了另一个。

Memory Analyzer1.3及之后的版本在检测到有多个Heap Dump快照后,就会弹出一个对话框,让用户自行选择需要的Heap Dump快照。

生成的索引文件的文件名中会包含一个快照的标识符,这样不同快照的索引文件就可以区别开来。这也意味着可以在Memory Analyzer中同时分析一个Heap Dump文件的多个内存快照。Memory Analyzer会记住上次打开的快照的信息,如果要打开另一个快照,可以在打开之前曾打开的快照后再将之关闭,之后再次点击File -> Open Heap Dump…菜单项打开原来的Heap Dump文件,此时就又可以重新选择要解析的快照。之前解析过的快照也可以通过已生成的索引文件再次打开,这样子就可以同时分析多个快照的信息了。

22

致一思维翻译整理

Memory Analyzer 使用文档

总结

下面是一个表格,总结了在不同平台上使用虚拟机设置以及工具来获取dump的方式(为了适应word稍稍调了一下,凑合看吧):

Vendor Release VM Parameter Sun Tools SAP

Tool

MAT

On out of

memory

On

Ctrl+Break

Agent JMap JConsole JVMMon acquire Heap Dump

Sun, HP 1.4.2_12 Yes Yes Yes No

1.5.0_07 Yes Yes (Since

1.5.0_15)

Yes Yes

(Only

Solaris

and

Linux)

Yes (Only Solaris

and Linux)

1.6.0_00 Yes No Yes Yes Yes Yes

1.7.0 Yes No Yes Yes Yes Yes

1.8.0 Yes No Yes Yes Yes Yes

SAP Any

1.5.0

Yes Yes Yes Yes

(Only

Solaris

and

Linux)

Yes

IBM 1.4.2

SR12

Yes Yes No No No No No

1.5.0

SR8a

Yes Yes No No No No No

1.6.0

SR2

Yes Yes No No No No No

23

致一思维翻译整理

Memory Analyzer 使用文档

Vendor Release VM Parameter Sun Tools SAP

Tool

MAT

1.6.0

SR6

Yes Yes No No No No Yes

1.7.0 Yes Yes No No No No Yes

1.8.0

Beta

Yes Yes No No No No Yes

2. 运行Leak Suspect Report

在打开dump文件时Memory Analyzer就会询问是否打开Leak Suspect Report或其他Report。在需要的时候可以点击Memory Analyzer工具栏上的Run Expert System Test -> Leak Suspects下拉菜单项。点击后就会打开一个HTML的报表,里面包含Heap Dump的概览图以及疑似内存泄露信息。

24

致一思维翻译整理

Memory Analyzer 使用文档

这个HTML报表文件以及Memory Analyzer生成的其它文件将和Heap Dump文件保存在一起,并在再次打开Heap Dump文件的时候省去解析的步骤。

Leck Suspect Report的部分内容提供了指向独立查询内容的链接,这为后续进一步的分析提供了便利。

25

致一思维翻译整理

Memory Analyzer 使用文档

想要了解更多关于Leak Suspect Report的内容,可以看一下这篇文章:Automated Heap Dump Analysis:

Finding Memory Leaks with One Click。

3. 列出最大的对象

Top Consumer查询可以按类、类加载器和包进行分组并找出每组最大的对象。在Memory Analyzer工具栏中依次打开如下选项Open Query Browser -> Leak Identification -> Top Consumer即可执行Top Consumer查询:

26

致一思维翻译整理

Memory Analyzer 使用文档

查询结果是一个HTML页,其中的对象会以超链接的形式展示,点击超链接,可以通过左键菜单项做进一步的的分析。

对象列表:

27

致一思维翻译整理

Memory Analyzer 使用文档

如前文所述,Top Consumer页按照类、类加载器和包也分别提供了对应的类似图表信息。

4. 找出Responsible Objects

在前面(概念->Dominator Tree)曾提到直接支配者(immediate dominator)的概念,也就是在对象引用关系图中,某个对象的支配者是要引用该对象必须经过的点,而直接支配者则是所有支配者中离这个对象最近的一个。我们也可以称直接支配者为对应对象的Responsible Object(责任对象),这里说的responsibility指的不只是提供了一个指向该对象的引用,而是保证这个对象的存活(alive)。

Memory Analyzer提供了immediate dominator查询,使用这个查询可以将一组相同类的对象的所有直接支配者找出来,这些支配者就是这组对象的Responsible Objects。因为每个对象只有一个直接支配者,这样就可以过滤掉大量的无聊的且让人头疼的底层引用(比如java.*这样的类),从而直接找到该对象在应用代码中的调用关系。

执行immediate dominator查询的方式:

 在histogram中选择相应的条目,在右键菜单中选择Immediate Dominators:

28

致一思维翻译整理

Memory Analyzer 使用文档

 在Memory Analyzer的工具栏中选择Query Browser > Immediate Dominators,随后会打开一个引导窗口,在引导窗口中输入要查询的对象类即可:

29

致一思维翻译整理

Memory Analyzer 使用文档

引导窗口:

下图是查询结果:

30

致一思维翻译整理

Memory Analyzer 使用文档

在查询结果图中列出了所有char[]数组对象的直接支配者对象。这些直接支配者保证了char[]对象的生存。因为在查询使用了-skip选项,略过了这样的JDK类的对象,所以我们在结果图中看到的大部分都是eclipse相关类(我们使用的Heap Dump就是eclipse的运行时dump)。

5. 使用OQL查询Heap对象

在Memory Analyzer中,用户可以使用类似SQL的查询语句查询在Heap中的对象。因为是查询对象的语句所以被称为OQL(Objects Query Language)。与SQL对应时,OQL中的类被视为表,类的实例被视为一行记录,类的属性被视为表中的字段。

SELECT *

FROM [ INSTANCEOF ]

[ WHERE ]

31

致一思维翻译整理

Memory Analyzer 使用文档

打开OQL编辑器可以使用Memory Analyzer工具栏上的 上方的文字输入区域;

 下方的查询结果展示区域。

图标。OQL编辑器窗口被分成了两部分:

输入查询语句后,可以按F5或者Ctrl+Enter按钮或者点击工具栏中的红色基础的OQL语法如下:

SELECT *

FROM [ INSTANCEOF ]

[ WHERE ]

在文字输入区域数据查询语句时,可以注意到Memory Analyzer提供了自动补全功能——可以针对类名、类名表达式、属性名、域名称和方法名进行自动补全。后文会有专门介绍OQL自动补全的内容。

在Memory AnalyzerPerspective中的Navigation History窗口下可以看到曾经执行过的OQL语句,双击其中的任意一条语句可以再次执行。

按钮来执行查询。

32

致一思维翻译整理

Memory Analyzer 使用文档

在查询结果窗口中,选择其中的一些对象,在右键菜单中执行Copy > OQL Query可以生成一条代表这些对象的OQL语句。生成的语句可以直接粘贴在OQL编辑器输入区域执行。

6. 类加载器分析

类加载器负责将类加载到JVM内存中。在分析堆内存时,重视对类加载器的分析出于两点考虑:

 首先,应用通常使用独立的类加载器加载应用组件(Component ,指的是同一个包下的所有类的集合或者是由同一个类加载器加载的全部类的集合);

 其次,被加载的类通常也是被保存在独立的空间中(比如permSpace),这些类也是可以被回收的。

要查看Heap Dump中的类加载器信息,可以在Memory Analyzer的工具栏中依次打开如下选项:Query

Browser > Java Basics > Class Loader Explorer。

33

致一思维翻译整理

Memory Analyzer 使用文档

简单说一下Class Loader Explorer中的表格:

Memory Analyzer为每个类加载器绑定了一个有意义的名称——如果使用了OSGi bundles,那这个名称就是bundle id。留神可能会有名称重复的情况。

在表格中紧挨着类加载器名称(Class Name)的是定义的类(Defined Class)和活动的实例的数量(No.

of Instances)。如果同一个组件被加载多次,那么活着的实例数量可以指示哪个类加载器更加有活力,以及哪个应该被垃圾回收掉。

7. 线程分析

Memory Analyzer提供了几种针对获取dump时的线程的查询方式。

线程概览(Overview)

要查看Heap Dump中所有的线程可以使用工具栏中的“Thread Overview”按钮,结果如下图所示。此34

致一思维翻译整理

Memory Analyzer 使用文档

外也可以执行Query Browser > Java Basics > Thread Overview and Stacks来获取线程概览图。

在查询结果中可以看到线程的名称、类加载器和相关的对象等信息。

一些Heap Dump文件(比如JVM6及以后的Heap Dump文件以及IBM虚拟机的系统dump文件)中包含了线程调用栈的信息以及每个栈帧的局部java对象。

探索线程调用栈和局部java 对象是一项非常强大的功能,提供了一种对Heap Dump这样的内存快照进行类似debug模式操作的功能。通过这个功能,我们可以深入分析一些内存密集型操作的细节。同时这也说明Heap Dump和Memory Analyzer不仅可以用于内存分析,也可以用来处理相当广泛地其他问题比如应用反应迟钝的问题。

线程细节(Thread Details)

可以使用右键菜单中的Java Basics > Thread Details来对单个线程进行分析。Memory Analyzer提供了一个扩展点,这样的扩展可以提供有关线程活动的语义信息。Thread Detail查询的结果中就包含了这些信息(如果可用的话)、一些概要信息以及线程堆栈跟踪信息。

对于基于DTFJ的dump(IBM系统dump以及IBM PHD文件相关的java dump),在Thread Details查询中可以看到更多的信息,包括线程状态、线程优先级以及native栈跟踪。

35

致一思维翻译整理

Memory Analyzer 使用文档

IBM虚拟机的dump中的线程栈

DTFJ解析器允许对线程栈的查看进行更多的控制。这个可以在DTFJ Parser的首选项(preferences)页中进行设置。有如下选项:

1. Normal

只在线程栈窗口展示栈帧(stack frame)信息。

2. 只将栈帧视为一个伪对象

将栈帧视为一个伪对象展示在所有的视图中,如Path to GC root、outbound references from threads(线程的对外引用)。栈帧中的局部变量被视为栈帧对象的对外引用。这有助于发现是哪些栈帧保证了对象的存活。不过这里栈帧对象的size指的是栈帧在虚拟机栈上占用的空间而非是在堆上占用的空间。

3. 将栈帧视为一个伪对象并将正在运行的方法视为一个伪类

将栈帧视为一个伪对象展示在所有的视图中,如Path to GC root、outbound references from threads(线程的对外引用)。栈帧中的局部变量被视为栈帧对象的对外引用。这有助于发现是哪些栈帧保证了对象的存活。在这里将栈帧中的方法视为一个伪类,并将之作为栈帧对象的类型。通过查找这种伪类的实例的数量,可以比较容易找出哪些方法在所有的线程中运行、哪些方法占用了大量的栈空间。这有助于解决StackOverflowErrors这样的问题。

4. 将栈帧视为一个伪对象并将所有的方法视为伪类

36

致一思维翻译整理

Memory Analyzer 使用文档

将栈帧视为一个伪对象展示在所有的视图中,如Path to GC root、outbound references from threads(线程的对外引用)。栈帧中的局部变量被视为栈帧对象的对外引用。这有助于发现是哪些栈帧保证了对象的存活。在这里将栈帧中的方法视为一个伪类,并将之作为栈帧对象的类型。通过查找这种伪类的实例的数量,可以比较容易找出哪些方法在所有的线程中运行、哪些方法占用了大量的栈空间。这有助于解决StackOverflowErrors这样的问题。其他所有的方法也被创建为对应伪类的对象。方法伪类对象的size就是字节码以及JIT码的size,在其他模式中这些会被累计入类定义占用空间的size中。这有助于找出哪些方法的字节码或机器码占用了大量的非堆内存。

可以分别看一下以上几种设置选项的视图:

1. Normal,栈帧不被视为是对象

2. 栈帧被视为伪类对象

注意,这里栈帧的类型是

37

致一思维翻译整理

Memory Analyzer 使用文档

3. 栈帧被视为伪对象,正在运行的方法被视为伪类

注意栈帧类型的不同,比如es([BIII)I;.

下图的Histogram视图中只是展示了将运行的方法视为伪类的信息,而类对象的size是0:

38

致一思维翻译整理

Memory Analyzer 使用文档

4. 将栈帧视为伪对象,并将所有的方法视为伪类

对外引用树和上一种设置是一样的,但是在类直方图中多了许多实例为0(即没有在运行)的伪类,同时伪类对象的size是一个非0的值。

8. 分析Java集合的使用

java中的集合使用存取并操作数据的对象。Memory Analyzer提供了如下查询工具来分析java集合:

Array Fill Ratio

Query

打印一个非直接类型数组的填充率频率分布。填充率指的是数组中非空元素的比例。因为直接类型数组不好判定是否为空,所以这个查询只对引用数组有效。

Arrays

Grouped by

Size Query

按size对数组进行分组。

39

致一思维翻译整理

Memory Analyzer 使用文档

Collection Fill

Ratio Query

打印指定集合的填充率频率分布。如下集合类型适用于该项查询:

ist

p

ble

ties

shMap

rentHashMap$Segment

ntextSupport

Local$ThreadLocalMap

eque

t

tyHashMap

tyQueue

shMap

rentHashMap

WriteArrayList

WriteArraySet

ueue

utes

Bindings

另外,可以通过“collection”,“size_attribute”和“array_attribute”等参数来指定自定义集合类型(非JDK集合类型)。

Collections

Grouped By

Size Query

按size对指定集合进行分组,适用于如下集合类型:

ist

p

p

ble

ties

shMap

rentHashMap

rentHashMap$Segment

eque

t

tyHashMap

List

tyQueue

40

致一思维翻译整理

Memory Analyzer 使用文档

t

rentSkipListMap

rentSkipListSet

WriteArrayList

WriteArraySet

ueue

BlockingDeque

BlockingQueue

onousQueue

utes

ntextSupport

Local$ThreadLocalMap

Bindings

ults

可以通过“collection”,“size_attribute”和“array_attribute”等参数来指定自定义集合类型(非JDK集合类型)。

Extract List

Values Query

列出链表中的元素。适用于LinkedList, ArrayList, Vector, CopyOnWriteArrayList,

PriorityQueue, ArrayDeque.

Hash Entries

Query

解析出HashMap和hashtable中的key-value对

Extract Hash

Set Value

Query

列出一个HashSet中的所有元素

Map Collision

Ratio Query

打印一个Map或类Map集合的碰撞率(这里说的是hash碰撞)的频率分布。主要适用于如下类型:

p

ties

ble

shMap

rentHashMap$Segment

p

t

ble

tyHashMap

rentHashMap

rentSkipListMap

rentSkipListSet

41

致一思维翻译整理

Memory Analyzer 使用文档

utes

ntextSupport

Local$ThreadLocalMap

Bindings

ults

可以通过“collection”,“size_attribute”和“array_attribute”等参数来指定自定义Map或类Map类型(非JDK集合类型)。

Primitive

Arrays with a

Constant Value

列出所有的直接类型数组(通过一个Pattern或OQL)并为所有的数组元素提供一个相同的值。

以上查询可以在Memory Analyzer工具栏中的下拉菜单Open Query Browser > Java Collections下找到,也可以在选定项的右键菜单-> Java Collections中找到。

9. 分析Finalizer

在Java的内部的垃圾回收机制清理对象时会执行Finalizer。因为无法控制finalizer的执行,所以通常建议不要使用它。又因为只有在finalize方法执行完成后才能实现内存的释放,所以finalizer中执行时间太长的任务会阻塞垃圾回收的完成。可以从query list中选择Finalizer Overview Query来获取finalizer overview:

42

致一思维翻译整理

Memory Analyzer 使用文档

Finalizer Query中包含以下子查询:

Finalizer in

Processing

解压正在被finalizer线程处理的对象。如果存在finalizer线程的话,这个查询将会返回正在被finalizer线程处理的对象。对象能被返回可能出于以下原因:

它(的finalize方法)被阻塞住了;

它(的finalize方法)执行时间很长;

finalizer队列满了。

可以使用finalizer queue查询来检查queue。

Ready for

Finalizer

Thread

这个查询展示了正在准备执行finalize方法的对象。亦即进入了finalizer queue的对象。如下原因可能导致finalizer queue填满:

当前处理的对象被阻塞住了或执行时间过长(可以使用Finalizer in Processing来检查);

当前应用中有太多对象使用finalize()方法导致它们都在内存中排队。

此外这里还会对对象提供一个类级别的汇总信息。

Finalizer

Thread

这个查询会列出正在执行对象finalize方法的后台线程。

43

致一思维翻译整理

Memory Analyzer 使用文档

Finalizer

Thread

Locals

这个查询将会列出执行对象finalize()的后台线程的线程本地变量。如果这里有记录存在,那就暗示着可能出现了对finalizer的错误(错误实现了finalize()方法)使用并有可能导致严重的问题(比如被finalizer线程长期持有的无用的内存或者finalizer处理的线程本地变量会影响程序的逻辑)。

10. 比较对象

在深入了解Memory Analyzer所提供的比较功能前容我们先做些解释。Memory Analyzer所提供Heap

Dump上的对象ID只是对象被定位到的地址。因为在GC期间,对象一直在被移来移去,JVM记录的地址也一直在变化。所以对象ID无法被用来比较对象。也就是说在比较两份不同的Heap Dump时,无法对具体的某个或某些对象进行比较,尽管这两份Heap Dump来自同一个进程。不过我们仍然可以对某一些聚集结果(比如说class Histogram)进行比较,来分析对象总量以及所占用的内存的变化。

Memory Analyzer所提供比较功能不只局限于两份Heap Dump文件的全局histogram的比较,它可以对任何数量的表格式的结果进行比较——比如说是三个不同对象的retained set——而不用管这些用来比较的表格是否来自同一个或者不同的Heap Dump。

这意味着我们可以做这样一些事:

 比较几个Heap Dump上的指定package的retained set;

 比较同一个应用上的不同对象A1、A2、A3(同一个Heap Dump)之间的不同之处。

下面的内容讲解了如何对不同的table进行比较。

1. 将所有要比较的table移到同一个Compare Basket(都装进菜篮子….)

在Memory Analyzer上执行过的所有查询都可以在Navigation History窗口(需要Memory Analysis

Perspective下查看)中找到。在这里可以将要比较的结果添加入Compare Basket。不过Navigation History是对应每个Heap Dump的,要比较同一个Heap Dump上的查询结果可以将之一起添加到Compare Basket中,要比较不同Heap Dump上的则需要一个一个地添加。

Tree(如dominator tree)也是可以进行比较的,不过在比较的时候为了方便会将之转为表格。此外OQL查询结果也是可以比较的,然而只有每个OQL 编辑器的最后一次查询结果才可以添加到Compare Basket中。如果在同一个OQL Editor又执行了新的查询,那么之前的查询结果将会从Compare Basket中被抹掉。如果需要比较两个OQL的查询结果,那么需要同时打开两个OQL Editor。

44

致一思维翻译整理

Memory Analyzer 使用文档

2. 调整要查询的表格的顺序

使用Compare Basket窗口中的工具条上的顺序调整工具可以调整要比较的表格的顺序,比如那个表格要放在最后面,哪个放在第一个等等:

3. 执行比较

调整好了顺序后,点击执行按钮执行比较:

要比较待比较表格中的一部分或者一些子集,可以选择要比较的表格,然后在右键菜单中选择相应的选项。此时,如果要比较的表格来自同一个Heap Dump,可以对比较结果执行一些不同的操作:

45

致一思维翻译整理

Memory Analyzer 使用文档

看一下比较结果:

4. 自定义要展示的结果

默认情况下在比较结果中展示的是所有比较项的绝对值,比如对象的数量、shallow size等等。不过用户可以将比较项的值调整为基准差,也可以选择比较哪些列。

46

致一思维翻译整理

Memory Analyzer 使用文档

上图默认以第0个要比较的表格为基准取的基准差。

上图选择了只展示Objects(对象数量)的比较信息。展示的数字仍然是取的基准差。下图是一张大图:

47

致一思维翻译整理

Memory Analyzer 使用文档

5. 右键菜单中的结果集操作

前文应该说过:如果要比较的结果表格来自同一个Heap Dump就可以对比较结果执行更丰富的操作。不过还得补充上一点:比较结果是通过右键菜单上的Compare Tables with all set operations生成的。满足了这两个条件后,在比较结果中点击右键就可以做更多的事情了:

48

致一思维翻译整理

Memory Analyzer 使用文档

11. 导出数据

分析数据也还可以导出的:

1. 使用工具栏上的导出按钮,导出格式可以选择(txt、csv和HTML三种格式):

2. Memory Analyzer中的视图都是html格式的,可以考虑直接复制粘贴,使用Ctrl+C、Ctrl+V。

3. 也可以使用右键菜单中的Copy功能进行复制再粘贴到目标位置上,如果要复制的内容巨多可以使用Copy -> Save Value to File选项将内容复制到文件中。

49

致一思维翻译整理


本文标签: 对象 文件 使用 内存 信息