MAT使用入門

原文出處: 高建武 (Granker,@高爺)   php

MAT簡html

MAT(Memory Analyzer Tool),一個基於Eclipse的內存分析工具,是一個快速、功能豐富的JAVA heap分析工具,它能夠幫助咱們查找內存泄漏和減小內存消耗。使用內存分析工具從衆多的對象中進行分析,快速的計算出在內存中對象的佔用大小,看看是誰阻止了垃圾收集器的回收工做,並能夠經過報表直觀的查看到可能形成這種結果的對象。java

MAT

 

MATandroid

固然MAT也有獨立的不依賴Eclipse的版本,只不過這個版本在調試Android內存的時候,須要將DDMS生成的文件進行轉換,才能夠在獨立版本的MAT上打開。不過Android SDK中已經提供了這個Tools,因此使用起來也是很方便的。c++

MAT工具的下載安裝

這裏是MAT的下載地址:https://eclipse.org/mat/downloads.php,下載時會提供三種選擇的方式:數據庫

Download MAT

 

Download MAT數組

  • Update Site 這種方式後面會有一個網址:好比http://download.eclipse.org/mat/1.4/update-site/ ,安裝過Eclipse插件的同窗應該知道,只要把這段網址複製到對應的Eclipse的Install New Software那裏,就能夠進行在線下載了。緩存

    MAT with eclipse

     

    MAT with eclipse安全

  • Archived Update Site 這種方式安裝的位置和上一種差很少,只不過第一種是在線下載,這一種是使用離線包進行更新,這種方式劣勢是當這個插件更新後,須要從新下載離線包,而第一種方式則能夠在線下載更新。dom

  • Stand-alone Eclipse RCP Applications 這種方式就是把MAT當成一個獨立的工具使用,再也不依附於Eclipse,適合不使用Eclipse而使用Android Studio的同窗。這種方式有個麻煩的地方就是DDMS導出的文件,須要進行轉換才能夠在MAT中打開。

下載安裝好以後,就可使用MAT進行實際的操做了。

Android(Java)中常見的容易引發內存泄露的不良代碼

使用MAT工具以前,要對Android的內存分配方式有基本的瞭解,對容易引發內存泄露的代碼也要保持敏感,在代碼級別對內存泄露的排查,有助於內存的使用。

Android主要應用在嵌入式設備當中,而嵌入式設備因爲一些衆所周知的條件限制,一般都不會有很高的配置,特別是內存是比較有限的。若是咱們編寫的代碼當中有太多的對內存使用不當的地方,不免會使得咱們的設備運行緩慢,甚至是死機。爲了可以使得Android應用程序安全且快速的運行,Android的每一個應用程序都會使用一個專有的Dalvik虛擬機實例來運行,它是由Zygote服務進程孵化出來的,也就是說每一個應用程序都是在屬於本身的進程中運行的。一方面,若是程序在運行過程當中出現了內存泄漏的問題,僅僅會使得本身的進程被kill掉,而不會影響其餘進程(若是是system_process等系統進程出問題的話,則會引發系統重啓)。另外一方面Android爲不一樣類型的進程分配了不一樣的內存使用上限,若是應用進程使用的內存超過了這個上限,則會被系統視爲內存泄漏,從而被kill掉。

常見的內存使用不當的狀況

  • 查詢數據庫沒有關閉遊標
    描述:
    程序中常常會進行查詢數據庫的操做,可是常常會有使用完畢Cursor後沒有關閉的狀況。若是咱們的查詢結果集比較小,對內存的消耗不容易被發現,只有在常時間大量操做的狀況下才會復現內存問題,這樣就會給之後的測試和問題排查帶來困難和風險。
    示例代碼:

     

     

     

     

     

    Java

     

    1

    2

    3

    4

    Cursor cursor = getContentResolver().query(uri ...);

      if (cursor.moveToNext()) {

       ... ...

    }


    修正示例代碼:

     

     

     

     

     

    Java

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    Cursor cursor = null;

    try {

        cursor = getContentResolver().query(uri ...);

      if (cursor != null & cursor.moveToNext()) {

      ... ...

      }

      } finally {

          if (cursor != null) {

      try {

          cursor.close();

      } catch (Exception e) {

          //ignore this

          }

      }

    }

  • 構造Adapter時,沒有使用緩存的 convertView
    描述:以構造ListView的BaseAdapter爲例,在BaseAdapter中提供了方法:

     

     

     

     

     

     

    Java

     

    1

    public View getView(int position, View convertView, ViewGroup parent)


    來向ListView提供每個item所須要的view對象。初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化必定數量的view對象,同時ListView會將這些view對象緩存起來。當向上滾動ListView時,原先位於最上面的list item的view對象會被回收,而後被用來構造新出現的最下面的list item。這個構造過程就是由getView()方法完成的,getView()的第二個形參 View convertView就是被緩存起來的list item的view對象(初始化時緩存中沒有view對象則convertView是null)。
    由此能夠看出,若是咱們不去使用convertView,而是每次都在getView()中從新實例化一個View對象的話,即浪費資源也浪費時間,也會使得內存佔用愈來愈大。ListView回收list item的view對象的過程能夠查看:android.widget.AbsListView.java –> void addScrapView(View scrap) 方法。

     

    示例代碼:

     

     

     

     

     

     

    Java

     

    1

    2

    3

    4

    5

    public View getView(int position, View convertView, ViewGroup parent) {

    View view = new Xxx(...);

    ... ...

    return view;

    }

    示例修正代碼:

     

     

     

     

     

     

    Java

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    public View getView(int position, View convertView, ViewGroup parent) {

    View view = null;

    if (convertView != null) {

    view = convertView;

    populate(view, getItem(position));

    ...

    } else {

    view = new Xxx(...);

    ...

    }

    return view;

    }

    關於ListView的使用和優化,能夠參這兩篇文章:

  • Bitmap對象不在使用時調用recycle()釋放內存
    描述:有時咱們會手工的操做Bitmap對象,若是一個Bitmap對象比較佔內存,當它不在被使用的時候,能夠調用Bitmap.recycle()方法回收此對象的像素所佔用的內存。
    另外在最新版本的Android開發時,使用下面的方法也能夠釋放此Bitmap所佔用的內存

     

     

     

     

     

    Java

     

    1

    2

    3

    4

    5

    Bitmap bitmap ;

    ...

    bitmap初始化以及使用

    ...

    bitmap = null;

  • 釋放對象的引用
    描述:這種狀況描述起來比較麻煩,舉兩個例子進行說明。

     

    示例A:
    假設有以下操做

     

     

     

     

     

    Java

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    public class DemoActivity extends Activity {

    ... ...

    private Handler mHandler = ...

    private Object obj;

    public void operation() {

      obj = initObj();

      ...

      [Mark]

      mHandler.post(new Runnable() {

             public void run() {

              useObj(obj);

             }

      });

    }

    }

    咱們有一個成員變量 obj,在operation()中咱們但願可以將處理obj實例的操做post到某個線程的MessageQueue中。在以上的代碼中,即使是mHandler所在的線程使用完了obj所引用的對象,但這個對象仍然不會被垃圾回收掉,由於DemoActivity.obj還保有這個對象的引用。因此若是在DemoActivity中再也不使用這個對象了,能夠在[Mark]的位置釋放對象的引用,而代碼能夠修改成:

     

     

     

     

     

    Java

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    public void operation() {

    obj = initObj();

    ...

    final Object o = obj;

    obj = null;

    mHandler.post(new Runnable() {

         public void run() {

             useObj(o);

         }

    }

    }

    示例B:
    假設咱們但願在鎖屏界面(LockScreen)中,監聽系統中的電話服務以獲取一些信息(如信號強度等),則能夠在LockScreen中定義一個PhoneStateListener的對象,同時將它註冊到TelephonyManager服務中。對於LockScreen對象,當須要顯示鎖屏界面的時候就會建立一個LockScreen對象,而當鎖屏界面消失的時候LockScreen對象就會被釋放掉。

    可是若是在釋放LockScreen對象的時候忘記取消咱們以前註冊的PhoneStateListener對象,則會致使LockScreen沒法被垃圾回收。若是不斷的使鎖屏界面顯示和消失,則最終會因爲大量的LockScreen對象沒有辦法被回收而引發OutOfMemory,使得system_process進程掛掉。

    總之當一個生命週期較短的對象A,被一個生命週期較長的對象B保有其引用的狀況下,在A的生命週期結束時,要在B中清除掉對A的引用。

  • 其餘
    Android應用程序中最典型的須要注意釋放資源的狀況是在Activity的生命週期中,在onPause()、onStop()、onDestroy()方法中須要適當的釋放資源的狀況。因爲此狀況很基礎,在此不詳細說明,具體能夠查看官方文檔對Activity生命週期的介紹,以明確什麼時候應該釋放哪些資源。

使用MAT進行內存調試

要調試內存,首先須要獲取HPROF文件,HPROF文件是MAT能識別的文件,HPROF文件存儲的是特定時間點,java進程的內存快照。有不一樣的格式來存儲這些數據,總的來講包含了快照被觸發時java對象和類在heap中的狀況。因爲快照只是一瞬間的事情,因此heap dump中沒法包含一個對象在什麼時候、何地(哪一個方法中)被分配這樣的信息。

使用Eclipse獲取HPROF文件

這個文件可使用DDMS導出,DDMS中在Devices上面有一排按鈕,選擇一個進程後(即在Devices下面列出的列表中選擇你要調試的應用程序的包名),點擊Dump HPROF file 按鈕:

Dump HEAP with DDMS

 

Dump HEAP with DDMS

選擇存儲路徑保存後就能夠獲得對應進程的HPROF文件。eclipse插件能夠把上面的工做一鍵完成。只須要點擊Dump HPROF file圖標,而後MAT插件就會自動轉換格式,而且在eclipse中打開分析結果。eclipse中還專門有個Memory Analysis視圖 ,獲得對應的文件後,若是安裝了Eclipse插件,那麼切換到Memory Analyzer視圖。使用獨立安裝的,要使用Android SDK自帶的的工具(hprof-conv 位置在sdk/platform-tools/hprof-conv)進行轉換

 

 

 

 

 

Java

 

1

hprof-conv xxx.xxx.xxx.hprof xxx.xxx.xxx.hprof

轉換事後的.hprof文件便可使用MAT工具打開了。

使用Android Studio獲取HPROF文件

使用Android Studio一樣能夠導出對應的HPROF文件:

Android-Studio

 

Android-Studio

最新版本的Android Studio得在文件上右鍵轉換成標準的HPROF文件,在能夠在MAT中打開。

MAT主界面介紹

這裏介紹的不是MAT這個工具的主界面,而是導入一個文件以後,顯示OverView的界面。

  1. 打開通過轉換的hprof文件:

    open hprof

     

    open hprof

    若是選擇了第一個,則會生成一個報告。這個無大礙。

    Leak Suspects

     

    Leak Suspects

  2. 選擇OverView界面:

    System OverView

     

    System OverView

    咱們須要關注的是下面的Actions區域

    Dominator Tree

     

    Dominator Tree

    • Top Consumers : 經過圖形列出最大的object

      Top Consumers

       

      Top Consumers

    • Duplicate Class:經過MAT自動分析泄漏的緣由

    • Histogram:列出內存中的對象,對象的個數以及大小

      Histogram

       

      Histogram

    • Dominator Tree:列出最大的對象以及其依賴存活的Object (大小是以Retained Heap爲標準排序的)

通常Histogram和 Dominator Tree是最經常使用的。

MAT中一些概念介紹

要看懂MAT的列表信息,Shallow heap、Retained Heap、GC Root這幾個概念必定要弄懂。

Shallow heap

Shallow size就是對象自己佔用內存的大小,不包含其引用的對象。

  • 常規對象(非數組)的Shallow size有其成員變量的數量和類型決定。

  • 數組的shallow size有數組元素的類型(對象類型、基本類型)和數組長度決定

由於不像c++的對象自己能夠存放大量內存,java的對象成員都是些引用。真正的內存都在堆上,看起來是一堆原生的byte[], char[], int[],因此咱們若是隻看對象自己的內存,那麼數量都很小。因此咱們看到Histogram圖是以Shallow size進行排序的,排在第一位第二位的是byte,char 。

Retained Heap

Retained Heap的概念,它表示若是一個對象被釋放掉,那會由於該對象的釋放而減小引用進而被釋放的全部的對象(包括被遞歸釋放的)所佔用的heap大小。因而,若是一個對象的某個成員new了一大塊int數組,那這個int數組也能夠計算到這個對象中。相對於shallow heap,Retained heap能夠更精確的反映一個對象實際佔用的大小(由於若是該對象釋放,retained heap均可以被釋放)。

這裏要說一下的是,Retained Heap並不老是那麼有效。例如我在A裏new了一塊內存,賦值給A的一個成員變量。此時我讓B也指向這塊內存。此時,由於A和B都引用到這塊內存,因此A釋放時,該內存不會被釋放。因此這塊內存不會被計算到A或者B的Retained Heap中。爲了糾正這點,MAT中的Leading Object(例如A或者B)不必定只是一個對象,也能夠是多個對象。此時,(A, B)這個組合的Retained Set就包含那塊大內存了。對應到MAT的UI中,在Histogram中,能夠選擇Group By class, superclass or package來選擇這個組。

爲了計算Retained Memory,MAT引入了Dominator Tree。加入對象A引用B和C,B和C又都引用到D(一個菱形)。此時要計算Retained Memory,A的包括A自己和B,C,D。B和C由於共同引用D,因此他倆的Retained Memory都只是他們自己。D固然也只是本身。我以爲是爲了加快計算的速度,MAT改變了對象引用圖,而轉換成一個對象引用樹。在這裏例子中,樹根是A,而B,C,D是他的三個兒子。B,C,D再也不有相互關係。把引用圖變成引用樹,計算Retained Heap就會很是方便,顯示也很是方便。對應到MAT UI上,在dominator tree這個view中,顯示了每一個對象的shallow heap和retained heap。而後能夠以該節點位樹根,一步步的細化看看retained heap究竟是用在什麼地方了。要說一下的是,這種從圖到樹的轉換確實方便了內存分析,但有時候會讓人有些疑惑。原本對象B是對象A的一個成員,但由於B還被C引用,因此B在樹中並不在A下面,而極可能是平級。

爲了糾正這點,MAT中點擊右鍵,能夠List objects中選擇with outgoing references和with incoming references。這是個真正的引用圖的概念,

  • outgoing references :表示該對象的出節點(被該對象引用的對象)。

  • incoming references :表示該對象的入節點(引用到該對象的對象)。

爲了更好地理解Retained Heap,下面引用一個例子來講明:

把內存中的對象當作下圖中的節點,而且對象和對象之間互相引用。這裏有一個特殊的節點GC Roots,這就是reference chain(引用鏈)的起點:

Paste_Image.png

 

Paste_Image.png

Paste_Image.png

 

Paste_Image.png

從obj1入手,上圖中藍色節點表明僅僅只有經過obj1才能直接或間接訪問的對象。由於能夠經過GC Roots訪問,因此左圖的obj3不是藍色節點;而在右圖倒是藍色,由於它已經被包含在retained集合內。
因此對於左圖,obj1的retained size是obj一、obj二、obj4的shallow size總和;
右圖的retained size是obj一、obj二、obj三、obj4的shallow size總和。
obj2的retained size能夠經過相同的方式計算。

GC Root

GC發現經過任何reference chain(引用鏈)沒法訪問某個對象的時候,該對象即被回收。名詞GC Roots正是分析這一過程的起點,例如JVM本身確保了對象的可到達性(那麼JVM就是GC Roots),因此GC Roots就是這樣在內存中保持對象可到達性的,一旦不可到達,即被回收。一般GC Roots是一個在current thread(當前線程)的call stack(調用棧)上的對象(例如方法參數和局部變量),或者是線程自身或者是system class loader(系統類加載器)加載的類以及native code(本地代碼)保留的活動對象。因此GC Roots是分析對象爲什麼還存活於內存中的利器。

MAT中的一些有用的視圖

Thread OvewView

Thread OvewView能夠查看這個應用的Thread信息:

Thread OvewView

 

Thread OvewView

Group

在Histogram和Domiantor Tree界面,能夠選擇將結果用另外一種Group的方式顯示(默認是Group by Object),切換到Group by package,能夠更好地查看具體是哪一個包裏的類佔用內存大,也很容易定位到本身的應用程序。

Group

 

Group

Path to GC Root

在Histogram或者Domiantor Tree的某一個條目上,右鍵能夠查看其GC Root Path:

Path to GC Root

 

Path to GC Root

這裏也要說明一下Java的引用規則:
從最強到最弱,不一樣的引用(可到達性)級別反映了對象的生命週期。

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

  • Soft Ref(軟引用):對應軟可達性,只要有足夠的內存,就一直保持對象,直到發現內存吃緊且沒有Strong Ref時纔回收對象。通常可用來實現緩存,經過java.lang.ref.SoftReference類實現。

  • Weak Ref(弱引用):比Soft Ref更弱,當發現不存在Strong Ref時,馬上回收對象而沒必要等到內存吃緊的時候。經過java.lang.ref.WeakReference和java.util.WeakHashMap類實現。

  • Phantom Ref(虛引用):根本不會在內存中保持任何對象,你只能使用Phantom Ref自己。通常用於在進入finalize()方法後進行特殊的清理過程,經過 java.lang.ref.PhantomReference實現。

點擊Path To GC Roots –> with all references

Path To GC Roots

 

Path To GC Roots

參文檔

    1. Shallow and retained sizes

    2. MAT的wiki:http://wiki.eclipse.org/index.php/MemoryAnalyzer

    3. http://cxy.liuzhihengseo.com/529.html  QQ技術交流羣290551701

相關文章
相關標籤/搜索