使用Mat分析大堆信息


    在定位一線問題時經常碰測試中出現Out Of Memory的問題, 通過jmap查看,發現JVM heap全用滿了。有很多工具可以查看JVM堆的信息, 收費的比如JProfiler, YourKit,免費的如Oracle JDK自帶的visualvm, jhat和Eclipse MAT。這個應用安裝在一台AWS上,沒有圖形界面, 內存也比較小,想通過VNC遠程桌面啟動visualvm或者MAT不可能,通過jhat分析dump出來的snapshot(大約4.3G)也很慢,半天沒有分析完畢,這種辦法也放棄。

   最后通過MAT的命令行工具分析了dump出來的snapshot,查找到OOM的元凶。

一、使用腳本分析Dump文件

  如果沒有辦法圖形化啟動visualvm和MAT,那么就使用MAT文件夾下的ParseHeapDump.sh, 特別適合分析大堆的信息
  首先你需要修改MemoryAnalyzer.ini中的Xmx值,確保有充足的硬盤空間(至少dump文件的兩倍)。然后運行:

  ./ParseHeapDump.sh heap.bin org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components

會得到suspects, overview和top_components三個視圖的信息:

  可以看到akka.dispatch.Dispatcher$$anon$1一個實例占用了2.4GB的內存,這就是罪魁禍首。這其實是akka dispatcher的mailbox中java.util.concurrent.ConcurrentLinkedQueue,每個Node占用了81M的內存,消息體太大了。

二、編寫程序得到所需信息

 也可以引 MAT的類,得到heap dump中的信息, 因為MAT使用Eclipse RCP框架, 基於osgi架構,使用起來不太方便,所以你可以別人抽取出來的MAT庫,如https://bitbucket.org/joebowbeer/andromat,然后實現一個命令行程序,比如下面的例子就是輸出所有的字符串的值:

import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.parser.model.PrimitiveArrayImpl;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.parser.internal.SnapshotFactory;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.util.ConsoleProgressListener;
import org.eclipse.mat.util.IProgressListener;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;

public class Main {
    public static void main(String[] args) throws SnapshotException, IOException {
        String arg = args[args.length - 1];
        String fileName = arg;
        IProgressListener listener = new ConsoleProgressListener(System.out);
        SnapshotFactory sf = new SnapshotFactory();
        ISnapshot snapshot = sf.openSnapshot(new File(fileName),new HashMap<String, String>(), listener);
        System.out.println(snapshot.getSnapshotInfo());
        System.out.println();
        String[] classNames = {"java.lang.String"};
        for (String name : classNames) {
            Collection<IClass> classes = snapshot.getClassesByName(name, false);
            if (classes == null || classes.isEmpty()) {
                System.out.println(String.format("Cannot find class %s in heap dump", name));
                continue;
            }
            assert classes.size() == 1;
            IClass clazz = classes.iterator().next();
            int[] objIds = clazz.getObjectIds();
            long minRetainedSize = snapshot.getMinRetainedSize(objIds, listener);
            System.out.println(String.format("%s instances = %d, retained size >= %d", clazz.getName(), objIds.length, minRetainedSize));
            for (int i = 0; i < objIds.length; i++) {
                IObject str = snapshot.getObject(objIds[i]);
                String address = Long.toHexString(snapshot.mapIdToAddress(objIds[i]));
                PrimitiveArrayImpl chars = (PrimitiveArrayImpl) str.resolveValue("value");
                String value = new String((char[]) chars.getValueArray());
                System.out.println(String.format("id=%d, address=%s, value=%s", objIds[i], address, value));

            }
        }
    }
}

 基本上使用ParseHeapDump.sh已經得到了我所需要的結果,優化akka actor消息的內容解決了我的問題。

三、mat常見功能的使用

1、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回收掉的

關於Histogram 的對比請參見:http://blog.csdn.net/lang_man_xing/article/details/22160849


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM