用eclipse memory analyzer分析heapdump

来源:互联网 发布:谷歌seo整站优化方案 编辑:程序博客网 时间:2024/05/21 11:06

eclipse memory analyzer分析heapdump

目录

一、工具介绍...1

二、heapdump实例分析...7

三、javacore实例分析...12

 

一、工具介绍

1.术语

Strong reference强引用):就是我们new出来的对象,但是还是被持有的应用,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题

soft reference(软引用):我们new出来的对象,但是已经不被具体对象持有,常见的就是缓存中的对象引用,垃圾回收在内存还够的时候不会回收该部分内存信息,只有在内存不够时才会回收这块引用的对象。软引用可用来实现内存敏感的高速缓存

weak reference弱引用):当对象不在有强引用时候,垃圾回收时立刻回收

Phantom reference虚引用:不会在内存中出现,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收

 

GC Roots:根元素。通常GC Roots是一个在当前线程的调用栈上的对象(例如方法参数和局部变量),或者是线程自身或者是system class loader(系统类加载器)加载的类以及native code(本地代码)保留的活动对象。

Shallow Size:

对象自身占用的内存大小,不包括它引用的对象。

针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包括一些java语言特性的数据存储单元。

针对数组类型的对象,它的大小是数组元素对象的大小总和。

Retained Size:

Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C, C就是间接引用)

换句话说,Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存。

不过,释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被被当做Garbage。

看图理解Retained Size

上图中,GC Roots直接引用了A和B两个对象。

A对象的Retained Size=A对象的Shallow Size

B对象的Retained Size=B对象的Shallow Size + C对象的Shallow Size

这里不包括D对象,因为D对象被GC Roots直接引用。

如果GC Roots不引用D对象呢?

此时, B对象的Retained Size=B对象的Shallow Size + C对象的Shallow Size + D对象的Shallow Size

 

2.实例

2.1导入一个内存文件后,用MAT打开,具体如下图所表示

 

2.2 details:一些基本信息

Size: 32.3 MB

Classes: 8.9k

Objects: 686.4k

Class Loader(类加载器BootstrapClassLoaderExtension ClassLoaderApplicationClassLoader): 250 Unreachable Objects Histogram

 

2.3 biggest object by retained size:显示在内存较大的对象信息

list objects -- with outgoing references : 查看这个对象持有的外部对象引用。

list objects -- with incoming references : 查看这个对象被哪些外部对象引用。

 

show objects by class  --  with outgoing references :查看这个对象类型持有的外部对象引用

show objects by class  --  with incoming references :查看这个对象类型被哪些外部对象引用

 

paths to gc root:显示不同类型引用(Strong,soft,weak )到跟节点的路径。

merge shorest path to gc root : 合并最短路径到root节点。

 

java basics:

    -- classloader 该对象对应的classloader信息。

    -- thread details :线程信息

    -- thread stacks  :线程堆栈

    -- find String : 在这个对象中查询需要的字符串

    -- group by : 根据某个字段统计出现的个数

 

java collections:这个暂时没研究。

 

leak Identification -- top consumers :几个大消耗内存的对象

 

2.4 可用操作

actions:列出每个类型的实例数及大小 。

Histogram :列出内存中的对象,对象的个数以及大小。

donimator tree :列出所有对象在整个内存对象中所占百分比。比较有用。

Top Consumers: 根据类名和包名列出开销最大的对象。

Duplicate Classes: 查找出在不同classloader中加载的相同类。

step by step 方式:

 

8显示的是应用程序中仍存在的所有异常,以及当异常抛出时所显示的消息。

提取与异常相关的额外信息

虽然从转储文件找到异常对象使您能够恢复异常消息,有时异常消息太过于普通或模糊,以致您无法理解问题的根源。其中一个很好的例子是java.net.ConnectException。如果尝试创建一个套接字连接来访问一个无法连接的主机时,您会得到下面的消息:

java.net.ConnectException: Connection refused: connect

     at java.net.PlainSocketImpl.socketConnect(Native Method)

     at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:352)

     at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:214)

     at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:201)

     at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:377)

 

     at java.net.Socket.connect(Socket.java:530)

     at java.net.Socket.connect(Socket.java:480)

     at java.net.Socket.(Socket.java:377)

     at java.net.Socket.(Socket.java:220)

 

如果您有创建这个套接字的代码,并且您能够看到代码所使用的主机名和端口,那么这个消息就已经足够。在更复杂的代码中,其主机名和端口会经常变化,因为它们是从外部来源获取(用户输入值、数据库等),那么这个消息可能无法帮助您理解为什么连接被拒绝。

堆跟踪应该包括一个套接字对象,其中包含了所需要的数据,而如果我们能够使用 Memory Analyzer从快照转储文件中查找这个套接字对象,然后我们就能够确定连接所拒绝的主机名和端口。

完成这个操作的最简单方法是在异常抛出后就生成一个转储文件。这在 IBM运行时环境中可以使用以下-Xdump选项集实现:

-Xdump:system:events=throw,range=1..1,

       filter=java/net/ConnectException#java/net/PlainSocketImpl.socketConnect

 

这个选项会在PlainSocketImpl.socketConnect()方法第一次出现ConnectException时生成一个 IBM系统转储文件。

在将所生成的转储文件加载到 Memory Analyzer之后,我们可以使用 Open Query Browser > Java Basics > Thread Stacks选项列出线程跟踪中与每一个方法相关的线程和对象。

通过展开当前线程和线程中的方法帧,您就能够查看与这些方法相关的对象。在遇到java.net.ConnectException时,最应该关注的方法是java.net.Socket.connect()。展开这个方法帧会显示内存中一个java.net.Socket对象的引用。这是我们尝试创建的套接字连接。

当Socket对象选中时,它的域会显示在 Inspector视图中,如图 9 所示:


9. Socket对象的 Inspector 视图

9中的信息并不是非常有用,因为Socket的实际实现位于impl域。您可以通过下面两种方法来检查impl对象的内容,一是展开Socket对象,然后选择主面板中的impl java.net.SocksSocketImpl,二是右键单击 Inspector 视图中的impl域,然后选择 Go Into。现在 Inspector视图会显示SocksSocketImpl的域,如图 10 所示:


10. SocksSocketImpl对象的 Inspector 视图

10所示的视图能够查看address和port域。在这里,端口是100,但是地址域指向一个java.net.Inet4Address对象。按照相同的过程来查看Inet4Address对象的域的结果如图 11 所示:


11. Inet4Address对象的 Inspector 视图

您会发现hostName被设置为baileyt60p。

技巧与方法

下面是一些有用的技巧和方法:

要注意 Memory Analyzer本身可能会遇到内存耗尽问题。对于 Eclipse MAT,要编辑 MemoryAnalyzer.ini文件中的-Xmx配置值。对于 ISA 版本,要编辑ISA install/rcp/eclipse/plugins/com.ibm.rcp.j2se.../jvm.properties 文件。

如果您的 32 Memory Analyzer 仍然遇到内存耗尽问题,可以改用 64 Eclipse MAT 或尝试使用傻瓜(headless)模式(见参考资料)。(ISA工具目前不支持 64 位平台。)

Memory Analyzer 会在转储文件的目录中写入“交换”文件,这会减少转储文件的重新加载时间。这些可以经过压缩,发送到另一台主机,然后保存到转储文件的相同目录中,这样就不需要重新加载完整的转储文件。

如果转储文件的大小在转储文件发生时与垃圾收集器不匹配,那么要查看 Overview选项卡中的 Unreachable Objects Histogram 链接。Java 堆可能会有很多的垃圾(例如,如果很长时间之前创建的集合有一段时间未使用)需要 Memory Analyzer删除。

如果两个对象A和B互相之间没有直接引用,但是都外部引用某个集合的对象C,那么C对象集合的 Retained Heap将不会包含在A或B占用集合中,而是包含在A与B拥用者的占用集合中。在某些情况下,B可能会临时观察集合C,它实际上是A的派生对象。在这种情况下,您可以右键单击A ,然后选择Java Basics > Customized Retained Set,并使用B的地址作为排除(-x)参数。

您可以一次加载多个转储文件,然后进行比较。打开较新转储文件的 Histogram,单击顶部的Compare,然后选择基线转储文件。

当您浏览一个引用树时,一定要知道这些引用可以直接或间接地指回一个“父”引用,这样您可以输入一个浏览循环或环路(例如,链表)。一定要知道对象的地址。此外,一定要知道如果对象的类名之前加上关键词class,那么您浏览的是这个类的静态实例。

大多数视图中显示的String值最多可以有 1,024个字符。如果您使用整个String,那么您可以右键单击该对象。然后选择 Copy > Save value to file

大多数视图都有一个导出按钮,而大多数 HTML结果都是在文件系统中创建的,所以数据可以导出共享或进行进一步的转换。与之相关的是,您可以使用组合键 Ctrl+C将表格中选中的任意行以文本方式复制到您的剪贴板中。

结束语

正如 Eclipse.org 所描述的,Memory Analyzer 一开始是作为“一个帮助您查询内存泄漏和减少内存消耗的快速富特性 Java堆分析器”而开发的。但是它的功能显然超越了所描述的范畴。除了具有分析“常见的”内存问题的作用,快照转储文件还可以作为其他判断问题技术的替代或补充,如跟踪技术和补丁技术。特别是对于 HPROF Dump IBM 系统转储文件,Memory Analyzer能够给您许多内存信息,如原始源代码使用的基本数据类型和域的名称。通过使用本文所介绍的各种视图,您可以查看遇到的问题,或者对它进行反向工程,包括总体覆盖问题和内存效率问题、Eclipse包和类加载器的关系、线程数据使用率和堆栈结果局部变量、异常等。OQL Memory Analyzer 插件模型也使您能够更容易地使用查询语言和编程方法来检查转储文件,这有助于实现常见分析的自动化。

 

二、heapdump实例分析

1、应用程序包准备(java应用)

import java.lang.ref.*; 

import java.util.*; 

 

class Grocery { 

   privatestaticfinalintSIZE = 100000;

   //属性d使得每个Grocery对象占用较多内存,有800K左右

   privatedouble[]d = newdouble[SIZE];

    private Stringid

 

    public Grocery(String id) { 

        this.id = id; 

   

 

    public String toString() { 

        returnid

   

 

    publicvoid finalize() { 

        System.out.println("Finalizing " +id); 

   

 

 

publicclass References { 

    privatestaticReferenceQueue rq =newReferenceQueue(); 

 

    publicstaticvoid checkQueue() { 

        Reference inq = rq.poll(); 

        //从队列中取出一个引用 

        if (inq !=null

            System.out.println("In queue: " + inq +" : " + inq.get()); 

   

 

    publicstaticvoid main(String[] args) { 

        finalint size = 10; 

        //创建10Grocery对象以及10个软引用 

        Set sa = new HashSet(); 

        for (int i = 0; i < size; i++) { 

            SoftReference ref =new SoftReference(new Grocery("soft" + i), rq)

            System.out.println("Just created soft: " + ref.get()); 

            sa.add(ref)

       

        System.gc(); 

        checkQueue(); 

        System.out.println("---------------------------------------------------"); 

        //创建10Grocery对象以及10个弱引用 

        Set wa = new HashSet(); 

        for (int i = 0; i < size; i++) { 

            WeakReference ref =new WeakReference(new Grocery ("weak " + i), rq)

            System.out.println("Just created weak: " + ref.get()); 

            wa.add(ref)

       

        System.gc(); 

        checkQueue(); 

        System.out.println("---------------------------------------------------"); 

        //创建10Grocery对象以及10个虚引用 

        Set pa = new HashSet(); 

        for (int i = 0; i < size; i++) { 

            PhantomReference ref =new PhantomReference(new Grocery("Phantom " + i),rq)

            System.out.println("Just created Phantom: " + ref.get()); 

            pa.add(ref)

       

        System.gc(); 

        checkQueue(); 

   

 

2jstat连接环境准备

nohup jstatd -J-Djava.rmi.server.hostname=10.10.230.220 -J-Djava.security.policy=jstatd.all.policy -p 8888 &

 

3、监控工具安装

1JDK1.7

2Eclipse Memory Analyzer

4、设置glassfish的集群JVM环境变量并重启集群

-Xloggc:/glassfish/gc.log

-XX:PermSize=20m

-XX:MaxPermSize=40m

-Xmx64m

-XX:HeapDumpPath=/glassfish/

-XX:+HeapDumpOnOutOfMemoryError

手工生产hprof文件:

jmap -dump:live,format=b,file=/glassfish/java_pid6622.hprof  22334

5、在glassfish中部署应用程序

6、查看日志检查部署失败原因

[#|2013-03-12T00:07:32.303+0800|INFO|glassfish3.1.2|ShoalLogger|_ThreadID=16;_ThreadName=Thread-2;|GMS1092: GMS View Change Received for group: dasc01 : Memb

ers in view for IN_DOUBT_EVENT(before change analysis) are :

……………………………………………..

 

[#|2013-03-12T00:07:32.549+0800|WARNING|glassfish3.1.2|ShoalLogger|_ThreadID=15;_ThreadName=Thread-2;|PermGen space

java.lang.OutOfMemoryError: PermGen space

7、查看是否产生heapdump文件

8jconsole查看permgen大小

9、下载用MAT分析heapdump文件

从下图初步分析是由于system class loader加载类时导致

10、通过dominator tree查看较大对象在整个内存对象中所占百分比

发现以下类占用比例较大

查看子类发现大量存在

11、通过java visualvm打开heapdump也发现问题

 

详细信息如下:

12、结合源代码分析,应该是在testgcload期间new大量对象导致溢出。

 

三、javacore实例分析

1、查找CPU资源占用较多的进程

[root@cc-ful2-01 ~]# top或者 ps -eo user,pid,tid,pcpu -T|grep PID|sort -rn |head -50

top - 11:25:33 up 94 days, 8:59,  1 user,  load average: 0.39, 0.36, 0.44

Tasks: 134 total,   1 running, 130 sleeping,   0 stopped,   3 zombie

Cpu(s):  1.0% us,  0.2% sy,  0.0% ni, 98.5% id,  0.2% wa,  0.0% hi,  0.0% si

Mem:  16631600k total, 14928504k used,  1703096k free,   441700k buffers

Swap:  4192924k total,     1152k used,  4191772k free,  6893988k cached

 

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                        

31058 was2      15   0  747m 431m  32m S    6  2.7  86:45.18 java                                                                                           

 3801 was2      22   0 1600m 1.5g  34m S    1  9.4 118:51.46 java                                                                                            

 7839 was1      16   0 1440m 1.2g  33m S    1  7.5  25:45.03 java                                                                                           

 2200 was2      17   0  898m 829m  33m S    0  5.1 135:25.05 java

2、strace跟踪操作系统进程

strace -o a.strace -f -F -p 31058 -p 3801 -p 7839

查看是否有读写操作和SystemOut或者SystemErr等关键词

-f :除了跟踪当前进程外,还跟踪其子进程。
  -o file :将输出信息写到文件file中,而不是显示到标准错误输出(stderr)。
  -p pid :绑定到一个由pid对应的正在运行的进程。此参数常用来调试后台进程。

-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.

3、查找资源占用较多的子进程

[root@cc-ful2-01 ~]# ps -eo user,pid,tid,pcpu -T|grep PID|sort -rn|head -50

was1      1426  9345  0.0

was1      1426  9134  0.0

was1      1426  8963  0.0

was1      1426  8875  0.0

4、 生产javacore

#./jstack -l 23163 >javacore.20100226.152745.11570.txt

生成core dump,文件名称类似于 javacore.20100226.152745.11570.txt

5、select to_char('27537','xxxxxxx') from dual   (词句需要在数据库环境执行,例如在PL/SQL工具下执行)

将第3步得到的TID 转换成16进制
6、用第5步得到的16进制数去 core dump里找到该子线程为何消耗CPU

关键字 native ID:

7、查找资料、联系业务使用的插件,确认故障线程所做操作由何原因导致

0 0