• 关于Heap Dump

    Heap Dump是什么? Heap Dump也叫堆转储文件,是一个Java进程在某个时间点上的内存快照。Heap Dump是有着多种类型的。不过总体上heap dump在触发快照的时候都保存了java对象和类的信息。通常在写heap dump文件前会触发一次FullGC,所以heap dump文件中保存的是FullGC后留下的对象信息。 我们可以通过Heap Dump做哪些事情? 一般在Heap Dump文件中可以获取到(这仍然取决于heap dump文件的类型)如下信息: 对象信息:类、成员变量、直接量以及引用值; 类信息:类加载器、名称、超类、静态成员; Garbage Collections Roots:JVM可达的对象; 线程栈以及本地变量:获取快照时的线程栈信息,以及局部变量的详细信息。 也就是说我们可以对上面这些内容进行分析。通常可以基于Heap Dump分析如下类型的问题: 找出内存泄漏的原因; 找出重复引用的jar或类; 分析集合的使用; 分析类加载器。 总而言之我们对Heap Dump的分析就是对应用的内存使用进行分析,从而更加合理地使用内存。 怎样获取Heap Dump? 获取heap dump有多种方式,可以通过参数配置在特定的条件下触发堆转储,也可以通过工具来获取。 1. 通过OutOfMemoryError获取heap dump 通过设置如下的JVM参数,可以在发生OutOfMemoryError后获取到一份HPROF二进制Heap Dump文件: 生成的文件会直接写入到工作目录。 这个方案适用于如下版本的虚拟机: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文件。 3. 使用HPROF agent 使用agent可以在程序执行结束时或者收到SIGQUIT信号时生成dump文件。使用agent也是要配置虚拟机参数的: 这种方式我没有试过,具体如何做的不是很清楚。 4. 使用jmap 这个算是用的最多的方式了吧。也很简单,执行下面的命令就可以了: 5. 使用jconsole 启动一个应用后,打开<JAVA_HOME>/bin/jconsole.exe,在jconsole中选择正在运行的应用: (在这个例子中选择的是eclipse,虽然进程名称为空,但是通过重启eclipse可以确认)。 建立连接后,选择页签MBean,执行com.sun.management. HotSpotDiagnostic下的操作dumpHeap。第一个参数p0是要获取的dump文件的完整路径名,记得文件要以.hprof作为扩展名(要在Memory AnalysisPerspective下打开扩展名必须是这个)。如果我们只想获取live的对象,第二个参数p1需要保持为true。 建议将导出的dump文件保存到一个独立的文件夹,在接下来的分析中会通过这个文件创建很多图表文件。 6. 使用Memory Analyzer 如果要获取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…菜单项。之后会打开如下窗口: 在上图中,根据具体的运行环境,预装的Heap Dump Provider按照默认设置展示了当前正在运行的java进程列表。一些Heap Dump Provider也允许(或者说需要)设置一些额外的参数(比如heap dump的类型),这个可以点击Configure按钮来设置。选择要dump的线程,点击Next按钮就可以生成dump文件了。 有的时候进程列表可能为空,这时有必要调整下Heap Dump Provider的设置了。点击Configure按钮,选择合适的provider并点击,然后找到要做调整的参数并做设置就好了。 此外,IBM的虚拟机还有自己的一套方法,这里就不写出了。 就这样! #######

    [阅读更多...]
  • 使用Memory Analyzer分析Java集合

    上一节里说了如何发现JVM内存中的空集合对象,这对减少JVM内存的浪费有些帮助。这一节要说的内容更有意思一些,可以让我们更深入地了解Java集合的使用。 先说明一点:这里仅仅是通过内存分析来得到一些结论。分析出来的结论通过JDK源码也许可以很轻易地得出,但是请关注分析过程和分析思路,毕竟源码不常有。 在上一节分析空集合对象的过程中难免会产生一些疑问:每个集合的长度有多大?这些集合实例由哪些对象持有?还有一些衍生出的问题:集合空间(capacity)的使用率(或者说填充率)有多高;集合类的hash函数是否足够好;当将元素填充进HashMap实例时是否会发生很多hash冲突?这些都是非常有意义的问题,有助于我们解决具体的任务。在设计开发阶段,这些问题未必都能得到解答,但是在运行测试阶段,借助MAT我们也许可以得到答案。 方案说明 这次会使用MAT中的Query Browser对dump文件进行切面式的分析。Query Browser我们之前已经多次使用,它提供了一系列非常有用的功能,就是这个:。这次主要是用到Java Collections功能组。说一下思路,目前我们有两种方案: 方案一:查询dump中某一个集合类型的全部对象(比如HashMap),然后对结果集进行分组。比如按size进行分组,然后查看size为1的集合对象的持有者。如果您的兴趣在于dump中的全部对象,那么这个方案就值得参考。 方案二:首先缩小要分析的对象的范围,而后使用Java Collection功能组进行分析。举个例子,首先筛选出类名符合“com.zhyea.projects.appname.*”的对象集合,而后在这些对象的histogram视图中再筛选出全部HashMap对象,而后执行查询查看“com.zhyea.projects.appname.*”对象集合中的HashMap对象的size分布信息。如果您的目标是服务器上的某个应用或者是应用中的某个模块,就可以试一下这个方案。 集合对象size 采用哪种结构存储数据以及分配多少初始化空间等内容是在开发阶段决定的,但是用来存储数据的集合对象通常是在运行阶段创建的。因此集合的size以及其持有者通常不是很明确。分析现实场景中得到的dump文件是验证之前采用的方案是否正确的一种方式。通过分析dump文件用户可以知道为集合对象分配的初始空间是否太小或者太大。太小了就会导致集合对象在运行时需要不断的重新调整大小,太大了则会导致不必要的内存浪费。 我取了一份公司测试环境中的dump文件,接下来将使用这份文件演示一下分析的过程。 接下来要做的事就是过滤并显示“com.joyxsys.projects.*”包下类的所有实例。通过执行这个操作我们可以只关注要查看的类,而不受dump中大量的其他对象的干扰。 这个需要用到Query Browser中的Show Retained Set功能: 点击菜单项后,在弹出窗口中输入参数信息: 然后就可以从现有的结果集中进一步过滤出符合“.*HashMap*”的对象: 过滤后的结果: 之后执行右键菜单中的“Collections Grouped By Size”菜单项: 执行菜单项后得到的结果是一个表格。表格的第一列是HashMap对象的大小,第二列是这样大小的HashMap对象的数量,再就是对应组中对象的Shallow Size和近似Retained Size: 如果对其中的某组数据感兴趣(比如长度为0的这组数据)就可以使用“Immediate dominators”这个菜单项来找出是谁持有这个组中的对象: 就如之前的经验,在这个面板中我们会看到一些类: 通过分析我们可以看到,在我的应用中共有104个HashMap对象,其中size为0的对象有93个。DelegatingClassLoader持有了90个size为0的HashMap对象。 集合填充率 “Collection Fill Ratio(集合填充率)”查询和刚才执行的查询在某种程度上很类似。不同之处在于Fill Ratio查询只对会为元素预先分配空间的集合类型有效,比如HashMap、ArrayList等等。Fill Ratio查询得到的信息是预分配空间的使用率,这个值通常在0~1之间,计算公式是 Fill Ratio = size / capacity。 对“com.joyxsys.projects.*”包下的HashMap实例执行Collection Fill Ratio: 查询结果如下图: 可以看到在104个HashMap实例中,93个是空的(和上面得到的结果一致),还有9个Fill Ratio不到20%。所有实例中Fill Ratio最高也不过40%。当然本身数据的总量就不够大,因此也说明不了多少问题。 通常可以把上面提到的两种方式结合起来分析问题。比如可以先执行”Collections Fill Ratio”,而后在得到的分组结果基础上执行”Collections Grouped By Size”操作。 Hash效率 现在开始讨论下一个问题——hashMap和Hashtable中的hash冲突。一个不甚高明的hash()方法实现会严重影响哈希表的查找速度。最为极端的一种情况就是所有元素返回的hash值都是一样的,这样每次查找就相当于遍历一个LinkedList。 哈希函数是否会导致太多哈希冲突也是一个在开发阶段不容易得到验证的问题,但同时也是通过分析dump文件容易得到结果的一个典型案例。尽管这问题和性能相关更多,但还是在dump中留下了相当多的痕迹,我们可以借助”Map Collision Ratio”来解决这个问题。通过”Map Collision Ratio”可以对HashMap(或Hashtable)的实例按哈希冲突的概率进行分组。哈希冲突的概率是向hash表中插入Entry时发生哈希冲突的概率。 仍然是分析刚才的dump文件,不过这次直接查看全部的HashMap实例,打开Histogram视图,过滤HashMap实例,之后再执行“Map Collision Ratio”查询: 执行查询后会得到一个表格,其中第一列是Collision Ratio,第二列是相应的对象的数目。 在这里可以很清楚地看到只有一个实例的Collision Ratio介于60%到80%之间。现在可以看一下这个实例,以及发生冲突的所有的key。首先要尝试获取相关的实例: 执行结果: 在这个里面是看不出什么来的,因为key的类型是String。关于String的hash函数是否够好我想不需多说。但是为什么还会出现这样的情况呢,我能想到的一个解释就是太巧了,碰巧大部分元素都是相同的hash函数。这样的一个HashMap实例会呈现出什么样的特点呢:就是会出现一个非常长的链表,hash表中几乎80%的元素都会在这个链表中。这个可以通过“Grouped By Size”和“List Objects”这两个工具来分析一下。 查看HashMap内容 最后介绍一个可以方便查看Map结构内容的工具:“Hash Entries”。在做性能分析工作时这个工具将会经常用到。 通过“List Objects”查看HashMap实例内容通常都不是一件容易的事情,关键是太容易受到干扰了,一大堆内容同时出现,尤其是Hash冲突比较严重的时候,需要不停的展开折叠内容。 看看上面这张图体验一下。 这时可以使用“Hash Entries”对一个或多个hashMap实例进行查询,查询结果依然是一个表格: 这时候是不是看起来舒服多了。现在可以使用右键菜单继续对目标实例进行分析了。 分析数组 在“Java Collections”功能组还提供了两个数组分析的工具(功能和我们前面介绍的类似,所以不会再重复说明了): Arrays Grouped By Size:对直接类型数组和对象数组都有效; Array Fill Ratio:对直接类型数组无效,统计数组中值不为null的元素的比例。 参考文档 Analyzing Java Collections Usage with MAT:http://scn.sap.com/people/krum.tsvetkov/blog/2007/11/05/analyzing-java-collections-usage-with-memory-analyzer

    [阅读更多...]
  • 找出被空集合占用的内存

    在平时的内存分析中发现有很大一部分集合对象在实例化后就从没有被使用过。集合类是我们平时使用最多的一种类,部分集合类在实例化时就会得到一部分空间(比如ArrayList、HashMap等)。这些空的集合实例虽然价值不大,但也有可能会浪费很多的内存空间。接下来演示一下如何去发现并解决这个问题。 案例 看一下下面这个MyValueStorage类的代码,在这个类里定义了三个ArrayList型的成员变量,并做了初始化操作。这个三个成员变量中standardValues将会被经常用到,specialValues偶尔会被用到,erroneousValues只有在极少数的情况下(比如发生异常)才会被使用到。 一个空的ArrayList默认初始化capacity是10,在32位操作系统上会占用80byte的内存空间,在64位操作系统上则会占用144byte。假使这个类在系统中被广泛的使用,在内存中有接近500 000个实例。那么在32位的系统中将会为specialValues和erroneousValues保留80 M的空间(在64位系统上是144MB)。一个应对的思路就是延迟初始化,即直到使用这些对象的时候才将之实例化,否则一直为null。当然我们需要做一些额外的工作,比如添加几个“if”语句以避免空指针异常的出现。 换个角度来考虑这个问题:如果对象的实例不多的话,那么为之进行优化的工作就是没有必要的。所以在着手优化之前,需要先弄清楚优化工作是否可以获取到明显的收益,在我们这个例子里就是是否可以显著地节省内存。 怎样找出未使用的集合实例 要找出未使用的集合实例可以遵循如下的步骤: 正常运行实例一段时间,根据线程ID获取heap dump文件; 使用Memory Analyzer中的OQL(Object Query Language)工具来查找大小是0且修改次数也是0的集合对象。即在我们获取dump文件之前,这些集合对象一直是空的且从未被修改过。 点击工具栏上的“OQL”按钮启动OQL工具: 顾名思义,OQL类似于SQL语句。这里我们使用如下的语句检查是否有空的且从未被使用过的 ArrayList, HashMap和Hashtable实例: 点击工具栏的红色叹号按钮执行查询。注意一次执行一行。 要想了解更多关于OQL的细节可以参考官方文档。此外,这篇文也不错,先将就着看,有时间整理一下。 计算空集合占用的内存 OQL查询得到的结果是一个符合查询要求的对象列表。想知道这些对象一共占用了多少内存可以开启histogram(直方图)视图。 然后计算所有对象占用的内存总量(使用工具栏上的“Calculate Retained Size”按钮,也可以在右键菜单中找到相关项),在下图实例中是266.4KB,还不算大。 这些空集合在哪儿 在确定了空集合的大小以后,如果发现有必要去做一些优化,那么接下来的事情就是找出谁制造了这些空集合。一个最快捷的方式就是使用右键菜单中的“Immediate Dominators”项。 结果如下图: 看到了这个结果以后相信会比较容易定位到需要优化的位置。对于我这个应用来说是然并卵——大部分都在框架上,幸好占用的空间不大,还不需要优化。 就这样!! 参考文档 Memory For Nothing:http://scn.sap.com/people/krum.tsvetkov/blog/2007/08/02/memory-for-nothing Analyzing a Heap Dump Using OQL:http://visualvm.java.net/oqlhelp.html ########

    [阅读更多...]
  • 使用Memory Analyzer分析内存泄漏

    概述 检测内存泄漏通常采用的方式是检查内存中某些对象的数量是否存在单调递增的现象。这可以通过“在线”实时监控分析的方式或者比较不同时段的内存快照来实现。然而实时监控的方案通常并不可行,尤其是在生产环境上,得考虑到因此导致的性能消耗;并且内存泄漏的产生通常也是非常偶然的,只有在某些特定条件下才会发生。这篇文章将介绍一些使用MAT发现内存泄漏的技巧。 准备工作 首先一定要有足够的数据,这里指的是heap dump文件。可以对JVM进行配置,以实现一发生OutOfMemoryError就自动生成Heap Dump文件。 第二步就是让内存泄漏问题清楚地暴露出来从而容易被捕捉到。这里有一个技巧:试着调整一下应用运行时的最大堆内存,调整到比应用正常运行所需的内存大一些就可以了(建议是一次Full GC后剩余内存的两倍大小)。即使不知道应用运行时到底需要多大的内存,加大堆内存也不是一个坏主意(有时可能真的就是内存不够用了,而非是发生了内存泄漏)。这里不讨论分配给一个应用太大的堆内存是好还是坏——这里只是将调整内存作为故障排除的一个临时方案。调整内存后我们会得到什么呢:如果确实存在内存泄漏,和内存泄漏有关的对象的大小估计会占到堆内存的一半(如果当时设置的是两倍),此时再找导致内存泄漏的原因应该就比较容易了。 案例一 现在假设我们已经做好了配置,然后某一天发生了MMO错误并生成了一个相当大的heap dump文件。接下来该怎么做呢?说实话,接下来要做的事情非常简单。 首先使用MAT打开这个heap dump文件。如果文件非常大的话,第一次打开可能会需要等一段时间,之后再打开这个文件就会非常快了,因为首次打开的时候已经完成了对文件的解析。现在开始尝试找出到底是谁蚕食了我们的内存。点击工具栏上的按钮进入Dominator tree视图: 这里会看到首页的对象图以一个树的形式展现出来。这个树里展示了对象、依赖、它们之间的引用关系以及其他。这里不会详细介绍这个树背后的全部细节,只是说一下两个重要的指标: 在树的最顶端(依Retained Heap Size排序)可以看到内存中最大的对象; 最大的对象就是占用内存最多的对象,它在树中的子节点都是被该对象直接或间接引用的对象(这意味着当这个对象被回收的时候它的子节点对象也会被回收)。 一般发生内存泄漏的时候,都会直接锁定那个最大的对象。接下来一步步接近真相:展开最大对象的子树,试着找到retained size最接近最大对象的子节点(通常是一个数组或者集合)。就是这么简单,我们找到了内存泄漏的元凶。如果还想继续探索下去的话,可以尝试探索更深的子节点。 看一下展开后的结果: 下一件事就是找到内存泄漏的对象到GCRoots的引用链。选中内存泄漏对象,右键菜单中选择“Paths from the GC roots -> without weak and soft references”即可: 在“Paths from the GC Roots”可以看到我们选中的对象到GCRoot的路径,最顶端是我们选中的目标,最下方是GCRoots。选的样本不好,估计到GCRoot还得展开好久: 换了一个样本,这下清晰多了: 案例二 如果所有的问题都能像上面的案例那样容易解决就太好了。有时候仅仅看一次dominator tree是远远不够的。看看下图的案例:这里也提供了足够多的内存让内存泄漏对象去生长,也打开一个覆盖了所有对象的dominator tree,其中就包括内存泄漏对象。但是能看到内存泄漏对象么?在案例一中,所有小的内存泄漏对象都被一个巨大的根对象引用,但是有时候这些相对较小的内存泄漏对象就直接在dominator tree的顶级节点上。尽管这些小的内存泄漏对象数量很多,但是每个对象的Retained Size都比较小,因此不会排在前面。 此时点击工具栏中的“Group by class”按钮会有很大的帮助: 点击按钮后,我们可以看到同一组对象Size的总和,此时再找内存泄漏的原因是不是会更容易一些: 其他 附上测试程序: 模拟时使用的虚拟机参数: 参考文档 Finding Memory Leaks with SAP Memory Analyzer Shallow heap & Retained heap

    [阅读更多...]
  • 使用MAT找出重复引用的jar或类

    这篇文章将介绍一种处理应用中的类(或库)的重复引用以及版本冲突问题的解决方案。这在应用集成阶段非常有效。适用于如下案例: NoClassDefFoundError:在classpath中有两个不同版本的同名类; 集成大型产品:检查同一个版本的jar包是否被重复引入,减少内存占用。 准备工作 安装Memory Analyzer Tools,在eclipse上搜mat插件、百度搜索、谷歌搜索都可以。 获取heap dump文件,参考这里:《关于Heap Dump》 步骤 使用MAT打开dump文件,执行Open Query browser->Java basics->Duplicated classes,如下图: 现在我们可以看到所有重复的类及相关的类加载器了: 需要注意的是:相关的类必须是被加载过才能找得到。 一个小技巧 既然我们已经做到这里了,顺便给您展示一下另外一个小tip。通过Inspector视图,可以看到被加载的类具体是在哪个jar包里。举例说,如果一个重复的类是被URLClassloader加载的,只需按以下的步骤执行: 选择目标类; 通过类属性页签进入Inspector视图; 右键点击“_context”属性; 最后点击“Go Into”。 在弹出的窗口窗口中,可以看到属性“_war”,然后就是被加载的类的位置了: ###### 本文译自下文:http://community.bonitasoft.com/blog/effective-way-fight-duplicated-libs-and-version-conflicting-classes-using-memory-analyzer-tool

    [阅读更多...]