Android調試工具 —— MAT

來源:互聯網
上載者:User

Android調試工具 —— MAT

一.概述

於大型 JAVA 應用程式來說,再精細的測試也難以堵住所有的漏洞,即便我們在測試階段進行了大量卓有成效的工作,很多問題還是會在生產環境下暴露出來,並且很難在測試環境中進行重現。JVM 能夠記錄下問題發生時系統的部分運行狀態,並將其儲存在堆轉儲 (Heap Dump) 檔案中,從而為我們分析和診斷問題提供了重要的依據。

通常記憶體泄露分析被認為是一件很有難度的工作,一般由團隊中的資深人士進行。不過,今天我們要介紹的 MAT(Eclipse Memory Analyzer)被認為是一個“傻瓜式“的堆轉儲檔案分析工具,你只需要輕輕點擊一下滑鼠就可以產生一個專業的分析報告。和其他記憶體泄露分析工具相比,MAT 的使用非常容易,基本可以實現一鍵到位,即使是新手也能夠很快上手使用。

MAT的使用是如此容易,你是不是也很有興趣來親自感受下呢,那麼第一步我們先來安裝 MAT。

二.安裝

Eclipse提供的一個記憶體分析工具。它是一個功能豐富的 JAVA 堆轉儲檔案分析工具,可以協助你發現記憶體漏洞和減少記憶體消耗。
官網地址:http://www.eclipse.org/mat

 

(如果你使用的是MOTODEV Studio for Android則預設內建了該工具)
你可以選擇安裝Eclipse提供的原版外掛程式或使用MOTODEVStudio for Android外掛程式

1.原版外掛程式
1).啟動Eclipse,一次點擊Help ->Intall New Software...;
2).點擊輸入欄右側小箭頭,選擇"Galileo"(我的是3.5版,不同版本要找到和自己版本名稱對應的);
3).等待列表更新完畢,找到並展開"General Purpose Tools";
4).選中並下載其中的"Memory Analyser (Incubation)"和"Memory Analyser (Charts)"兩項。

外掛程式安裝完畢,你還需要重新啟動 Eclipse 的工作平台。

比較而言,單機版的安裝方式非常簡單,使用者只需要下載相應的安裝包,然後解壓縮即可運行,這也是被普遍採用的一種安裝方式。在下面的例子裡,我們使用的也是單機版的 MAT。具體的下載要求和地址可參見其產品下載頁面:http://www.eclipse.org/mat/downloads.php。

另外,如果你需要用 MAT 來分析 IBM JVM 產生的 dump 檔案的話,還需要額外安裝 IBM Diagnostic ToolFramework ,具體的下載和安裝配置步驟請參見:http://www.ibm.com/developerworks/java/jdk/tools/dtfj.html

2.MOTODEV Studio for Android外掛程式
1).啟動Eclipse,一次點擊Help -> Intall NewSoftware...;
2).通過Add建立一個更新(最新地址可在此獲得):
    Name:MOTODEV Studio for Android Plugin
   Location:https://studio-android.motodevupdate.com/android/2.0
3).下載並安裝此更新。
* 如果主要針對Android開發,建議使用MOTODEVStudio for Android的整合外掛程式,應為它還提供了很多便利的工具,不過如果已經安裝了ADT的需要先手動卸載才能安裝,具體請參考:作為外掛程式安裝 MOTODEV Studio。

 

三.為什麼用MAT

在平時工作過程中,有時會遇到OutOfMemoryError,我們知道遇到Error一般表明程式存在著嚴重問題,可能是災難性的。所以找出是什麼原因造成OutOfMemoryError非常重要。之前的觀點,我認為使用即時profiling/monitoring之類的工具,用一種非常即時的方式來分析哪裡存在記憶體流失是很正確的。年初使用了某profiler工具測試訊息中介軟體中存在的記憶體流失,發現在輸送量很高的時候profiler工具自己也無法響應,這讓人很頭痛。後來瞭解到這樣的工具本身就要消耗效能,且在某些條件下還發現不了泄漏。所以,分析離線資料就非常重要了,MAT正是這樣一款工具。

 

四.為何會記憶體溢出

我們知道JVM根據generation(代)來進行GC,根據所示,一共被分為younggeneration(年輕代)、tenuredgeneration(老年代)、permanentgeneration(永久代, perm gen),permgen(或稱Non-Heap 非堆)是個異類,稍後會講到。注意,heap空間不包括perm gen。

絕大多數的對象都在young generation被分配,也在younggeneration被收回,當young generation的空間被填滿,GC會進行minor collection(次回收),這次回收不涉及到heap中的其他generation,minor collection根據weak generationalhypothesis(弱年代假設)來假設younggeneration中大量的對象都是垃圾需要回收,minor collection的過程會非常快。young
generation中未被回收的對象被轉移到tenuredgeneration,然而tenured generation也會被填滿,最終觸發major collection(主回收),這次回收針對整個heap,由於涉及到大量對象,所以比minor collection慢得多。

 

JVM有三種記憶體回收行程,分別是throughput collector,用來做並行young generation回收,由參數-XX:+UseParallelGC啟動;concurrent low pausecollector,用來做tenured generation並發回收,由參數-XX:+UseConcMarkSweepGC啟動;incremental lowpause collector,可以認為是預設的記憶體回收行程。不建議直接使用某種記憶體回收行程,最好讓JVM自己決斷,除非自己有足夠的把握。

 

Heap中各generation空間是如何劃分的?通過JVM的-Xmx=n參數可指定最大heap空間,而-Xms=n則是指定最小heap空間。在JVM初始化的時候,如果最小heap空間小於最大heap空間的話,如所示JVM會把未用到的空間標註為Virtual。除了這兩個參數還有-XX:MinHeapFreeRatio=n和 -XX:MaxHeapFreeRatio=n來分別控制最大、最小的剩餘空間與使用中的物件之比例。在32位Solaris SPARC作業系統下,預設值如下,在32位windows
xp下,預設值也差不多。

 

參數

預設值

MinHeapFreeRatio

40

MaxHeapFreeRatio

70

-Xms

3670k

-Xmx

64m

 

由於tenured generation的majorcollection較慢,所以tenured generation空間小於young generation的話,會造成頻繁的major collection,影響效率。Server JVM預設的young generation和tenured generation空間比例為1:2,也就是說young generation的eden和survivor空間之和是整個heap(當然不包括perm gen)的三分之一,該比例可以通過-XX:NewRatio=n參數來控制,而Client
JVM預設的-XX:NewRatio是8。至於調整young generation空間大小的NewSize=n和MaxNewSize=n參數就不講了,請參考後面的資料。

 

young generation中倖存的對象被轉移到tenured generation,但不幸的是concurrent collector線程在這裡進行major collection,而在回收任務結束前空間被耗盡了,這時將會發生Full Collections(Full GC),整個應用程式都會停止下來直到回收完成。Full GC是高負載生產環境的噩夢……

現在來說說異類perm gen,它是JVM用來儲存無法在Java語言級描述的對象,這些對象分別是類和方法資料(與class loader有關)以及interned strings(字串駐留)。一般32位OS下perm gen預設64m,可通過參數-XX:MaxPermSize=n指定,JVM Memory Structure一文說,對於這塊地區,沒有更詳細的文獻了,神秘。

 

回到問題“為何會記憶體溢出?”。

要回答這個問題又要引出另外一個話題,既什麼樣的對象GC才會回收?當然是GC發現通過任何reference chain(引用鏈)無法訪問某個對象的時候,該對象即被回收。名詞GC Roots正是分析這一過程的起點,例如JVM自己確保了對象的可到達性(那麼JVM就是GC Roots),所以GC Roots就是這樣在記憶體中保持對象可到達性的,一旦不可到達,即被回收。通常GC Roots是一個在current thread(當前線程)的call stack(調用棧)上的對象(例如方法參數和局部變數),或者是線程自身或者是system
class loader(系統類別載入器)載入的類以及native code(本地代碼)保留的使用中的物件。所以GC Roots是分析對象為何還存活於記憶體中的利器。知道了什麼樣的對象GC才會回收後,再來學習下對象引用都包含哪些吧。

 

從最強到最弱,不同的引用(可到達性)層級反映了對象的生命週期。

l  Strong Ref(強引用):通常我們編寫的代碼都是Strong Ref,於此對應的是強可達性,只有去掉強可達,對象才被回收。

l  Soft Ref(軟引用):對應軟可達性,只要有足夠的記憶體,就一直保持對象,直到發現記憶體吃緊且沒有Strong Ref時才回收對象。一般可用來實現緩衝,通過java.lang.ref.SoftReference類實現。

l  Weak Ref(弱引用):比SoftRef更弱,當發現不存在Strong Ref時,立刻回收對象而不必等到記憶體吃緊的時候。通過java.lang.ref.WeakReference和java.util.WeakHashMap類實現。

l  Phantom Ref(虛引用):根本不會在記憶體中保持任何對象,你只能使用Phantom Ref本身。一般用於在進入finalize()方法後進行特殊的清理過程,通過 java.lang.ref.PhantomReference實現。

 

有了上面的種種我相信很容易就能把heap和perm gen撐破了吧,是的利用Strong Ref,儲存大量資料,直到heap撐破;利用interned strings(或者class loader載入大量的類)把perm gen撐破。

 

關於shallow size、retained size

 

Shallow size就是對象本身佔用記憶體的大小,不包含對其他對象的引用,也就是對象頭加成員變數(不是成員變數的值)的總和。在32位系統上,對象頭佔用8位元組,int佔用4位元組,不管成員變數(對象或數組)是否引用了其他對象(執行個體)或者賦值為null它始終佔用4位元組。故此,對於String對象執行個體來說,它有三個int成員(3*4=12位元組)、一個char[]成員(1*4=4位元組)以及一個對象頭(8位元組),總共3*4 +1*4+8=24位元組。根據這一原則,對String a=”rosenjiang”來說,執行個體a的shallow
size也是24位元組(很多人對此有爭議,請看官甄別並留言給我)。

 

Retained size是該對象自己的shallow size,加上從該對象能直接或間接訪問到對象的shallow size之和。換句話說,retained size是該對象被GC之後所能回收到記憶體的總和。為了更好的理解retained size,不妨看個例子。

 

把記憶體中的對象看成中的節點,並且對象和對象之間互相引用。這裡有一個特殊的節點GC Roots,正解!這就是reference chain的起點。

從obj1入手,中藍色節點代表僅僅只有通過obj1才能直接或間接訪問的對象。因為可以通過GC Roots訪問,所以左圖的obj3不是藍色節點;而在右圖卻是藍色,因為它已經被包含在retained集合內。

所以對於左圖,obj1的retained size是obj1、obj2、obj4的shallow size總和;右圖的retained size是obj1、obj2、obj3、obj4的shallow size總和。obj2的retained size可以通過相同的方式計算。

 

Heap Dump

 

heap dump是特定時間點,java進程的記憶體快照。有不同的格式來儲存這些資料,總的來說包含了快照被觸發時java對象和類在heap中的情況。由於快照只是一瞬間的事情,所以heap dump中無法包含一個對象在何時、何地(哪個方法中)被分配這樣的資訊。

 

在不同平台和不同java版本有不同的方式擷取heap dump,而MAT需要的是HPROF格式的heapdump二進位檔案。想無需人工幹預的話,要這樣配置JVM參數:-XX:-HeapDumpOnOutOfMemoryError,當錯誤發生時,會自動產生heap dump,在生產環境中,只有用這種方式。如果你想自己控制什麼時候產生heapdump,在Windows+JDK6環境中可利用JConsole工具,而在Linux或者Mac OS X環境下均可使用JDK5、6內建的jmap工具。當然,還可以配置JVM參數:-XX:+HeapDumpOnCtrlBreak,也就是在控制台使用Ctrl+Break鍵來產生heap
dump。由於我是windows+JDK5,所以選擇了-XX:-HeapDumpOnOutOfMemoryError這種方式,更多配置請參考MAT Wiki。

 

參考資料

MAT Wiki

Interned Strings

Strong,Soft,Weak,Phantom Reference

Tuning GarbageCollection with the 5.0 Java[tm] Virtual Machine

Permanent Generation

UnderstandingWeak References譯文

JavaHotSpot VM Options

Shallow and retained sizes

JVM Memory Structure

GC roots

 

五.準備heap dump

請看下面的Pilot類,沒啥特殊的。

/**
 * Pilot class
 * @author rosen jiang
 */
package org.rosenjiang.bo;

public class Pilot{
    
    String name;
    int age;
    
    public Pilot(String a, int b){
        name = a;
        age = b;
    }
}

然後再看OOMHeapTest類,它是如何撐破heap dump的。

/**
 * OOMHeapTest class
 * @author rosen jiang
 */
package org.rosenjiang.test;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.rosenjiang.bo.Pilot;

public class OOMHeapTest {
    public static void main(String[] args){
        oom();
    }
    
    private static void oom(){
        Map<String, Pilot> map = new HashMap<String, Pilot>();
        Object[] array = new Object[1000000];
        for(int i=0; i<1000000; i++){
            String d = new Date().toString();
            Pilot p = new Pilot(d, i);
            map.put(i+"rosen jiang", p);
            array[i]=p;
        }
    }
}

是的,上面構造了很多的Pilot類執行個體,向數組和map中放。由於是Strong Ref,GC自然不會回收這些對象,一直放在heap中直到溢出。當然在運行前,先要在Eclipse中配置VM參數 -XX:+HeapDumpOnOutOfMemoryError。好了,一會兒功夫記憶體溢出,控制台打出如下資訊。

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3600.hprof 
Heap dump file created [78233961 bytes in 1.995 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

java_pid3600.hprof既是heap dump,可以在OOMHeapTest類所在的工程根目錄下找到。

MAT安裝
話分兩頭說,有了heap dump還得安裝MAT。可以在http://www.eclipse.org/mat/downloads.php選擇合適的方式安裝。安裝完成後切換到Memory Analyzer視圖。在Eclipse的左上方有Open Heap Dump按鈕,按照剛才說的路徑找到java_pid3600.hprof檔案並開啟。解析hprof檔案會花些時間,然後會彈出嚮導,直接Finish即可。稍後會看到所示的介面。

MAT工具分析了heap dump後在介面上非常直觀的展示了一個餅圖,該圖深色地區被懷疑有記憶體流失,可以發現整個heap才64M記憶體,深色地區就佔了99.5%。接下來是一個簡短的描述,告訴我們main線程佔用了大量記憶體,並且明確指出system class loader載入的"java.lang.Thread"執行個體有記憶體聚集,並建議用關鍵字"java.lang.Thread"進行檢查。所以,MAT通過簡單的兩句話就說明了問題所在,就算使用者沒什麼處理記憶體問題的經驗。在下面還有一個"Details"連結,在點開之前不妨考慮一個問題:為何對象執行個體會聚集在記憶體中,為何存活(而未被GC)?是的——Strong
Ref,那麼再走近一些吧。

點擊了"Details"連結之後,除了在上一頁看到的描述外,還有Shortest Paths To the Accumulation Point和Accumulated Objects部分,這裡說明了從GC root到聚集點的最短路徑,以及完整的reference chain。觀察Accumulated Objects部分,java.util.HashMap和java.lang.Object[1000000]執行個體的retained heap(size)最大,在上一篇文章中我們知道retained heap代表從該類執行個體沿著reference
chain往下所能收集到的其他類執行個體的shallow heap(size)總和,所以明顯類執行個體都聚集在HashMap和Object數組中了。這裡我們發現一個有趣的現象,既Object數組的shallow heap和retained heap竟然一樣,通過Shallow and retained sizes一文可知,數組的shallow heap和一般對象(非數組)不同,依賴於數組的長度和裡面的元素的類型,對數組求shallow
heap,也就是求數組集合內所有對象的shallow heap之和。好,再來看org.rosenjiang.bo.Pilot對象執行個體的shallow heap為何是16,因為對象頭是8位元組,成員變數int是4位元組、String引用是4位元組,故總共16位元組。

接著往下看,來到了Accumulated Objects by Class地區,顧名思義,這裡能找到被聚集的對象執行個體的類名。org.rosenjiang.bo.Pilot類上頭條了,被執行個體化了290,325次,再返回去看程式,我承認是故意這麼乾的。還有很多有用的報告可用來協助分析問題,只是本文中的例子太簡單,也用不上。以後如有用到,一定撰文詳細敘述。

 

參照:

http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html

http://www.blogjava.net/rosen/archive/2010/06/13/323522.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.