編輯:關於android開發
原文出處: 高建武 (Granker,@高爺)
MAT簡介
MAT(Memory Analyzer Tool),一個基於Eclipse的內存分析工具,是一個快速、功能豐富的JAVA heap分析工具,它可以幫助我們查找內存洩漏和減少內存消耗。使用內存分析工具從眾多的對象中進行分析,快速的計算出在內存中對象的占用大小,看看是誰阻止了垃圾收集器的回收工作,並可以通過報表直觀的查看到可能造成這種結果的對象。
MAT
當然MAT也有獨立的不依賴Eclipse的版本,只不過這個版本在調試Android內存的時候,需要將DDMS生成的文件進行轉換,才可以在獨立版本的MAT上打開。不過Android SDK中已經提供了這個Tools,所以使用起來也是很方便的。
這裡是MAT的下載地址:https://eclipse.org/mat/downloads.php,下載時會提供三種選擇的方式:
Download MAT
MAT with eclipse
下載安裝好之後,就可以使用MAT進行實際的操作了。
使用MAT工具之前,要對Android的內存分配方式有基本的了解,對容易引起內存洩露的代碼也要保持敏感,在代碼級別對內存洩露的排查,有助於內存的使用。
Android主要應用在嵌入式設備當中,而嵌入式設備由於一些眾所周知的條件限制,通常都不會有很高的配置,特別是內存是比較有限的。如果我們編寫的代碼當中有太多的對內存使用不當的地方,難免會使得我們的設備運行緩慢,甚至是死機。為了能夠使得Android應用程序安全且快速的運行,Android的每個應用程序都會使用一個專有的Dalvik虛擬機實例來運行,它是由Zygote服務進程孵化出來的,也就是說每個應用程序都是在屬於自己的進程中運行的。一方面,如果程序在運行過程中出現了內存洩漏的問題,僅僅會使得自己的進程被kill掉,而不會影響其他進程(如果是system_process等系統進程出問題的話,則會引起系統重啟)。另一方面Android為不同類型的進程分配了不同的內存使用上限,如果應用進程使用的內存超過了這個上限,則會被系統視為內存洩漏,從而被kill掉。
示例代碼:
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的使用和優化,可以參考這兩篇文章:
示例A:
假設有如下操作
我們有一個成員變量 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的引用。
要調試內存,首先需要獲取HPROF文件,HPROF文件是MAT能識別的文件,HPROF文件存儲的是特定時間點,java進程的內存快照。有不同的格式來存儲這些數據,總的來說包含了快照被觸發時java對象和類在heap中的情況。由於快照只是一瞬間的事情,所以heap dump中無法包含一個對象在何時、何地(哪個方法中)被分配這樣的信息。
這個文件可以使用DDMS導出,DDMS中在Devices上面有一排按鈕,選擇一個進程後(即在Devices下面列出的列表中選擇你要調試的應用程序的包名),點擊Dump HPROF file 按鈕:
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
最新版本的Android Studio得在文件上右鍵轉換成標准的HPROF文件,在可以在MAT中打開。
這裡介紹的不是MAT這個工具的主界面,而是導入一個文件之後,顯示OverView的界面。
一般Histogram和 Dominator Tree是最常用的。
要看懂MAT的列表信息,Shallow heap、Retained Heap、GC Root這幾個概念一定要弄懂。
Shallow size就是對象本身占用內存的大小,不包含其引用的對象。
因為不像c++的對象本身可以存放大量內存,java的對象成員都是些引用。真正的內存都在堆上,看起來是一堆原生的byte[], char[], int[],所以我們如果只看對象本身的內存,那麼數量都很小。所以我們看到Histogram圖是以Shallow size進行排序的,排在第一位第二位的是byte,char 。
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。這是個真正的引用圖的概念,
為了更好地理解Retained Heap,下面引用一個例子來說明:
把內存中的對象看成下圖中的節點,並且對象和對象之間互相引用。這裡有一個特殊的節點GC Roots,這就是reference chain(引用鏈)的起點:
Paste_Image.png
Paste_Image.png
從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可以通過相同的方式計算。
GC發現通過任何reference chain(引用鏈)無法訪問某個對象的時候,該對象即被回收。名詞GC Roots正是分析這一過程的起點,例如JVM自己確保了對象的可到達性(那麼JVM就是GC Roots),所以GC Roots就是這樣在內存中保持對象可到達性的,一旦不可到達,即被回收。通常GC Roots是一個在current thread(當前線程)的call stack(調用棧)上的對象(例如方法參數和局部變量),或者是線程自身或者是system class loader(系統類加載器)加載的類以及native code(本地代碼)保留的活動對象。所以GC Roots是分析對象為何還存活於內存中的利器。
Thread OvewView可以查看這個應用的Thread信息:
Thread OvewView
在Histogram和Domiantor Tree界面,可以選擇將結果用另一種Group的方式顯示(默認是Group by Object),切換到Group by package,可以更好地查看具體是哪個包裡的類占用內存大,也很容易定位到自己的應用程序。
Group
在Histogram或者Domiantor Tree的某一個條目上,右鍵可以查看其GC Root Path:
Path to GC Root
這裡也要說明一下Java的引用規則:
從最強到最弱,不同的引用(可到達性)級別反映了對象的生命周期。
點擊Path To GC Roots –> with all references
Path To GC Roots
Android 手機衛士--自定義屬性,android衛士在前面的文章中,已經實現了“設置中心”第一欄的功能以及布局 本文地址:http://w
[android] 手機衛士黑名單功能(ListView結合SQLite增刪改),androidsqlite修改界面,在頂部橫條上增加一個添加按鈕,點擊打開一個自定義對話
Android-Universal-Image-Loader (圖片異步加載緩存庫)對Bitmap的優化處理 前言: 前面兩篇分別介紹了: Android-Unive
我的Android進階之旅------)Java文件大小轉換工具類 (B,KB,MB,GB,TB,PB之間的大小轉換) Java文件大小轉換工具類 (B,KB,MB,