Tomcat内存优化4.1续 ——使用MAT分析内存泄露

案例1:

概述

对于大型 JAVA 应用程序来说,再精细的测试也难以堵住所有的漏洞,即便我们在测试阶段进行了大量卓有成效的工作,很多问题还是会在生产环境下暴露出来,并且很难在测试环境中进行重现。JVM 能够记录下问题发生时系统的部分运行状态,并将其存储在堆转储 (Heap Dump) 文件中,从而为我们分析和诊断问题提供了重要的依据。

通常内存泄露分析被认为是一件很有难度的工作,一般由团队中的资深人士进行。不过,今天我们要介绍的 MAT(Eclipse Memory Analyzer)被认为是一个“傻瓜式“的堆转储文件分析工具,你只需要轻轻点击一下鼠标就可以生成一个专业的分析报告。和其他内存泄露分析工具相比,MAT 的使用非常容易,基本可以实现一键到位,即使是新手也能够很快上手使用。

MAT 的使用是如此容易,你是不是也很有兴趣来亲自感受下呢,那么第一步我们先来安装 MAT。

准备环境和测试数据

我们使用的是 Eclipse Memory Analyzer V0.8,Sun JDK 6

安装 MAT

和其他插件的安装非常类似,MAT 支持两种安装方式,一种是“单机版“的,也就是说用户不必安装 Eclipse IDE 环境,MAT 作为一个独立的 Eclipse RCP 应用运行;另一种是”集成版“的,也就是说 MAT 也可以作为 Eclipse IDE 的一部分,和现有的开发平台集成。

集成版的安装需要借助 Update Manager。

如图 1 所示,首先通过 Help -> Software Updates... 启动软件更新管理向导。

图 1. 安装插件第一步
图 1. 安装插件第一步

选择“Available Software“然后按如图 2 所示的方式添加 MAT 的更新地址 http://download.eclipse.org/technology/mat/0.8/update-site/

图 2. 安装插件第二步
图 2. 安装插件第二步

如图 3 所示,接下来选择你想要安装的 MAT 的功能点,需要注意的是 Memory Analyzer (Chart) 这个功能是一个可选的安装项目,它主要用来生成相关的报表,不过如果需要用到这个功能,你还需要额外的安装 BIRT Chart Engine。

图 3. 安装插件第三步
图 3. 安装插件第三步

插件安装完毕,你还需要重新启动 Eclipse 的工作平台。

比较而言,单机版的安装方式非常简单,用户只需要下载相应的安装包,然后解压缩即可运行,这也是被普遍采用的一种安装方式。在下面的例子里,我们使用的也是单机版的 MAT。具体的下载要求和地址可参见其产品下载页面:http://www.eclipse.org/mat/downloads.php

另外,如果你需要用 MAT 来分析 IBM JVM 生成的 dump 文件的话,还需要额外安装 IBM Diagnostic Tool Framework ,具体的下载和安装配置步骤请参见:http://www.ibm.com/developerworks/java/jdk/tools/dtfj.html

配置环境参数

安装完成之后,为了更有效率的使用 MAT,我们还需要做一些配置工作。因为通常而言,分析一个堆转储文件需要消耗很多的堆空间,为了保证分析的效率和性能,在有条件的情况下,我们会建议分配给 MAT 尽可能多的内存资源。你可以采用如下两种方式来分配内存更多的内存资源给 MAT。

一种是修改启动参数 MemoryAnalyzer.exe -vmargs -Xmx4g

另一种是编辑文件 MemoryAnalyzer.ini,在里面添加类似信息 -vmargs – Xmx4g。

至此,MAT 就已经成功地安装配置好了,开始进入实战吧。

获得堆转储文件

巧妇难为无米之炊,我们首先需要获得一个堆转储文件。为了方便,本文采用的是 Sun JDK 6。通常来说,只要你设置了如下所示的 JVM 参数:

-XX:+HeapDumpOnOutOfMemoryError

JVM 就会在发生内存泄露时抓拍下当时的内存状态,也就是我们想要的堆转储文件。

如果你不想等到发生崩溃性的错误时才获得堆转储文件,也可以通过设置如下 JVM 参数来按需获取堆转储文件。

-XX:+HeapDumpOnCtrlBreak

除此之外,还有很多的工具,例如 JMap,JConsole 都可以帮助我们得到一个堆转储文件。本文实例就是使用 JMap 直接获取了 Eclipse Galileo 进程的堆转储文件。您可以使用如下命令:

JMap -dump:format=b,file=<dumpfile> <pid>

不过,您需要了解到,不同厂家的 JVM 所生成的堆转储文件在数据存储格式以及数据存储内容上有很多区别, MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如 Sun, HP, SAP 所采用的 HPROF 二进制堆存储文件,以及 IBM 的 PHD 堆存储文件等都能被很好的解析(您需要安装额外的插件,请参考 相关说明,本文不作详细解释)。

万事俱备,接下来,我们就可以开始体验一键式的堆存储分析功能了。

生成分析报告

首先,启动前面安装配置好的 Memory Analyzer tool , 然后选择菜单项 File- Open Heap Dump 来加载需要分析的堆转储文件。文件加载完成后,你可以看到如图 4 所示的界面:

图 4. 概览
图 4. 概览

通过上面的概览,我们对内存占用情况有了一个总体的了解。先检查一下 MAT 生成的一系列文件。

图 5. 文件列表
图 5. 文件列表

可以看到 MAT 工具提供了一个很贴心的功能,将报告的内容压缩打包到一个 zip 文件,并把它存放到原始堆转储文件的存放目录下,这样如果您需要和同事一起分析这个内存问题的话,只需要把这个小小的 zip 包发给他就可以了,不需要把整个堆文件发给他。并且整个报告是一个 HTML 格式的文件,用浏览器就可以轻松打开。

接下来我们就可以来看看生成的报告都包括什么内容,能不能帮我们找到问题所在吧。您可以点击工具栏上的 Leak Suspects 菜单项来生成内存泄露分析报告,也可以直接点击饼图下方的 Reports->Leak Suspects 链接来生成报告。

图 6. 工具栏菜单
图 6. 工具栏菜单

分析三步曲

通常我们都会采用下面的“三步曲”来分析内存泄露问题:

首先,对问题发生时刻的系统内存状态获取一个整体印象。

第二步,找到最有可能导致内存泄露的元凶,通常也就是消耗内存最多的对象

接下来,进一步去查看这个内存消耗大户的具体情况,看看是否有什么异常的行为。

下面将用一个基本的例子来展示如何采用“三步曲”来查看生产的分析报告。

查看报告之一:内存消耗的整体状况

图 7. 内存泄露分析报告
图 7. 内存泄露分析报告

如图 7 所示,在报告上最醒目的就是一张简洁明了的饼图,从图上我们可以清晰地看到一个可疑对象消耗了系统 99% 的内存。

在图的下方还有对这个可疑对象的进一步描述。我们可以看到内存是由 java.util.Vector 的实例消耗的,com.ibm.oti.vm.BootstrapClassLoader 负责这个对象的加载。这段描述非常短,但我相信您已经可以从中找到很多线索了,比如是哪个类占用了绝大多数的内存,它属于哪个组件等等。

接下来,我们应该进一步去分析问题,为什么一个 Vector 会占据了系统 99% 的内存,谁阻止了垃圾回收机制对它的回收。

查看报告之二:分析问题的所在

首先我们简单回顾下 JAVA 的内存回收机制,内存空间中垃圾回收的工作由垃圾回收器 (Garbage Collector,GC) 完成的,它的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。

在垃圾回收机制中有一组元素被称为根元素集合,它们是一组被虚拟机直接引用的对象,比如,正在运行的线程对象,系统调用栈里面的对象以及被 system class loader 所加载的那些对象。堆空间中的每个对象都是由一个根元素为起点被层层调用的。因此,一个对象还被某一个存活的根元素所引用,就会被认为是存活对象,不能被回收,进行内存释放。因此,我们可以通过分析一个对象到根元素的引用路径来分析为什么该对象不能被顺利回收。如果说一个对象已经不被任何程序逻辑所需要但是还存在被根元素引用的情况,我们可以说这里存在内存泄露。

现在,让我们开始真正的寻找内存泄露之旅,点击“Details ”链接,可以看到如图 8 所示对可疑对象 1 的详细分析报告。

图 8. 可疑对象 1 的详细分析报告
图 8. 可疑对象 1 的详细分析报告
  1. 我们查看下从 GC 根元素到内存消耗聚集点的最短路径:
图 9. 从根元素到内存消耗聚集点的最短路径
图 9. 从根元素到内存消耗聚集点的最短路径

我们可以很清楚的看到整个引用链,内存聚集点是一个拥有大量对象的集合,如果你对代码比较熟悉的话,相信这些信息应该能给你提供一些找到内存泄露的思路了。

接下来,我们再继续看看,这个对象集合里到底存放了什么,为什么会消耗掉如此多的内存。

图 10. 内存消耗聚集对象信息
图 10. 内存消耗聚集对象信息

在这张图上,我们可以清楚的看到,这个对象集合中保存了大量 Person 对象的引用,就是它导致的内存泄露。

至此,我们已经拥有了足够的信息去寻找泄露点,回到代码,我们发现,是下面的代码导致了内存泄露 :

清单 1. 内存泄漏的代码段
     
     
while (1<2)
{
Person person = new Person("name","address",i);
v.add(person); person = null;
}

总结

从上面的例子我们可以看到用 MAT 来进行堆转储文件分析,寻找内存泄露非常简单,尤其是对于新手而言,这是一个很好的辅助分析工具。但是,MAT 绝对不仅仅是一个“傻瓜式”内存分析工具,它还提供很多高级功能,比如 MAT 支持用 OQL(Object Query Language)对 heap dump 中的对象进行查询,支持对线程的分析等,有关这些功能的使用可以参考 MAT 的帮助文档。

参考资料





案例2:

1 内存泄漏的排查方法

Dalvik Debug Monitor Server (DDMS) 是 ADT插件的一部分,其中有两项功能可用于内存检查 :

· heap 查看堆的分配情况

· allocation tracker跟踪内存分配情况

DDMS 这两项功能有助于找到内存泄漏的操作行为。

Eclipse Memory Analysis Tools (MAT) 是一个分析 Java堆数据的专业工具,用它可以定位内存泄漏的原因。

工具地址 : https://www.eclipse.org/mat/

1.1 观察 Heap

· 运行程序,然后进入 DDMS管理界面,如下:

\

PS : 点击工具栏上的 \来更新统计信息

点击右侧的 Cause GC 按钮或工具栏上的 \即可查看当前的堆情况,如下:

\

主要关注两项数据:

o Heap Size 堆的大小,当资源增加,当前堆的空余空间不够时,系统会增加堆的大小,若超过上限 (例如 64M,视平台和具体机型而定)则会被杀掉

o Allocated 堆中已分配的大小,这是应用程序实际占用的内存大小,资源回收后,此项数据会变小

· 查看操作前后的堆数据,看是否有内存泄漏 
对单一操作(比如添加页,删除页)进行反复操作,如果堆的大小一直增加,则有内存泄漏的隐患。

1.2 利用MAT分析内存堆

DDMS 可以将当前的内存 Dump成一个 hprof格式的文件,MAT 读取这个文件后会给出方便阅读的信息,配合它的查找,对比功能,就可以定位内存泄漏的原因。

· 获取 hprof文件 
点击工具栏上的 \按钮,将内存信息保存成文件。 如果是用 MAT Eclipse 插件获取的 Dump文件,则不需要经过转换,Adt会自动进行转换然后打开。

· 转换 hprof文件 
DDMS Dump 出的文件要经过转换才能被 MAT识别,Android SDK提供了这个工具 hprof-conv (位于 sdk/tools下)

· ./hprof-conv xxx-a.hprof xxx-b.hprof

· 用 MAT打开转换后的 hprof文件

\

1.3 Histogram 查询

用的最多的功能是 Histogram,点击 Actions下的 Histogram项将得到 Histogram结果:

\

它按类名将所有的实例对象列出来,可以点击表头进行排序,在表的第一行可以输入正则表达式来匹配结果 :

\

在某一项上右键打开菜单选择 list objects ->with incoming refs 将列出该类的实例:

\

它展示了对象间的引用关系,比如展开后的第一个子项表示这个 HomePage(0x420ca5b0)被 HomePageContainer(0x420c9e40)中的 mHomePage属性所引用.

快速找出某个实例没被释放的原因,可以右健 Path to GC Roots-->exclue all phantom/weak/soft etc. reference :

\

得到的结果是:

\

从表中可以看出 PreferenceManager -> … ->HomePage这条线路就引用着这个 HomePage实例。用这个方法可以快速找到某个对象的 GC Root,一个存在 GC Root的对象是不会被 GC回收掉的.

1.4 Histogram 对比

为查找内存泄漏,通常需要两个 Dump结果作对比,打开 Navigator History面板,将两个表的 Histogram结果都添加到 Compare Basket中去 :

\

添加好后,打开 Compare Basket面板,得到结果:

\

点击右上角的 ! 按钮,将得到比对结果:

\

注意,上面这个对比结果不利于查找差异,可以调整对比选项:

\

再把对比的结果排序,就可得到直观的对比结果:

\

也可以对比两个对象集合,方法与此类似,都是将两个 Dump结果中的对象集合添加到Compare Basket中去对比。找出差异后用 Histogram查询的方法找出 GC Root,定位到具体的某个对象上。

1.5 例子

举例一个典型的分析内存泄漏的过程:

1. 使用 Heap查看当前堆大小为 23.00M

2. 添加一个页后堆大小变为 23.40M

3. 将添加的一个页删除,堆大小为 23.40M

4. 多次操作,结果仍相似,说明添加/删除页存在内存泄漏 (也应注意排除其它因素的影响)

5. Dump 出操作前后的 hprof 文件 (1.hprof,2.hprof),用 mat打开,并得到 histgram结果

6. 使用 HomePage字段过滤 histgram结果,并列出该类的对象实例列表,看到两个表中的对象集合大小不同,操作后比操作前多出一个 HomePage,说明确实存在泄漏

7. 将两个列表进行对比,找出多出的一个对象,用查找 GC Root的方法找出是谁串起了这条引用线路,定位结束

PS :

· 很多时候堆增大是 Bitmap引起的,Bitmap在 Histogram中的类型是 byte [],对比两个 Histogram中的 byte[] 对象就可以找出哪些 Bitmap有差异

· 多使用排序功能,对找出差异很有用

2 内存泄漏的原因分析

总结出来只有一条: 存在无效的引用! 
良好的模块设计以及合理使用设计模式有助于解决此问题。

3 Tips

· 使用 android:largeHeap="true"标记 (API Level >= 11) 
在 AndroidManifest.xml中的 Application节点中声明即可分配到更大的堆内存, android:largeHeap标记在 Android系统应用中也有广泛的应用 ,比如 Launcher, Browser这些内存大户上均有使用.

4 参考

· DDMS 官方教程 http://developer.android.com/tools/debugging/ddms.html

· MAT 下载 http://www.eclipse.org/mat/downloads.php

· MAT 使用 http://android-developers.blogspot.tw/2011/03/memory-analysis-for-android.html



案例3:


对于大型服务端应用程序来说,有些内存泄露问题很难在测试阶段发现,此时就需要分析JVM Heap Dump文件来找出问题。随着单机内存越来越大,应用heap也开得越来越大,动辄十几G的Dump也不足为奇了。要快速分析,快速定位问题就必须有给力的工具帮忙,下面我来介绍下常用内存分析工具。

    内存分析工具

    jmap

    JDK自带的一个工具,是JVM Heap导出的必备工具。

    jmap -dump:format=b,file=xxx.bin pid pid是java程序pid

    此命令会将虚拟机heap镜像导成文件。不过jmap也有直接分析功能:jmap –histo pid,如下图。优点是可以直接查看对象的大小和类型,缺点是无法查看详细的对象引用信息。

    

    jhat

    JDK自带的dump文件分析工具,会启动一个webserver,可以直接浏览对象大小、类型及对象引用信息。缺点是对于动辄几十G的dump文件力不从心,分析时间长而且web界面会因为对象太多而无响应或者OOM。

    jhat -J-mx512m -port <端口号:默认为7000> xxx.bin -mx512m表示所用最大内存512m

    MAT

    Eclipse Memory Analyzer是一个非常好用的内存dump文件分析工具,我们可以利用它的Eclipse 插件轻松实现查看对象树、对象大小、生成报告,甚至自动化分析可能出现泄露的对象。关于MAT的使用介绍可以参考:http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html?ca=drs-。 文章中的例子是在windows平台下分析,对于非常大的dump文件就无能为力了。

    Linux下使用MAT

    对于非常大的dump文件MAT同样有办法分析,有下面几个步骤:

    Step 1:下载MAT的Stand-alone Eclipse RCP Applications,下载地址:http://www.eclipse.org/mat/downloads.php。在“Stand-alone Eclipse RCP Applications”中找到适合自己的版本。找一台足够大内存的linux机器,将MAT复制上去。

    Step 2:进入mat所在目录,编辑MemoryAnalyzer.ini文件设置最大内存值比如-Xmx9g。

    Step 3:执行./ParseHeapDump.sh xxxx.bin 来分析dump文件,MAT的分析速度还是很快的。最终得到以下文件。

    

    Step 4:将分析得到的文件包括原dump文件下载回windows平台,打开eclipse插件使用菜单File–>Open Heap Dump打开dump文件即可查看到分析结果。

    MAT中我们最常用的是Dominator Tree(List the biggest objects and what they keep alive.)功能来分析较大的objects以及他们之间的引用关系,确定一些对象为什么不会被gc。比如从下面两张图中就可以看出,在两个前后两次的dump文件中HConnectionManager$HConnectionImplementation对象越来越大,这里就有OOM的风险。

    

    


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值