Dalvik 虛擬機器支援垃圾收集,但是這不意味著你可以不用關心記憶體管理。你應該格外注意行動裝置的記憶體使用量,手機和平板的記憶體空間是受到限制的。
在這篇文章裡面,我們來看看Android SDK裡面的一些記憶體剖析工具(profiling tools)是如何協助我們修整應用程式的記憶體使用量。
一、 記憶體泄露
一些記憶體使用量問題是很明顯的,例如,如果在每次使用者觸控螢幕幕的時候應用程式有記憶體泄露,將會有可能觸發OutOfMemoryError,最終程式崩潰。
另外一些問題卻很微妙,也許只是降低應用程式和整個系統的效能(當高頻率和長時間地運行垃圾收集器的時候)。
二、 記憶體工具
Android SDK 提供了2個主要的剖析應用程式記憶體使用量情況的工具:Allocation Tracker 和 heap dumps
1)Allocation Tracker是很有用的,特別是當你想得到程式在一定的時間裡記憶體的分配情況的一種感性認識的時候。但是它不能給你任何關於程式heap總體情況的任何 資訊。關於Allocation Tracker的更多資訊,請看文章Tracking Memory Allocations
2)heap dumps,它是更強大的記憶體分析工具,一個heap dump就是一個程式heap的快照,它儲存為一種叫做HPROF的二進位格式。Dalvik用的也是類似的格式,但是不完全一樣,這裡是Java 的HPROF工具 。有很多方法去產生一個運行時應用程式的heap dump,其中一種就是使用在DDMS裡邊的Dump HPROF file按鈕,如果想產生更精確的dump資料,可以在程式中使用android.os.Debug.dumpHprofData() 方法。
分析heap dump,你可以使用一些標準的工具比如 jhat 或者Eclipse MAT(Memory Analyzer Tool) 。不過,首先需要把.hprof檔案從Dalvik格式轉換成J2SE HPROF格式,你可以使用Android SDK提供的hprof-conv工具。例如:
hprof-conv dump.hprof converted-dump.hprof
三、 記憶體調試
Dalvik 運行時裡,程式員不能顯式地分配和釋放記憶體,所以這裡的記憶體泄露跟C和C++裡面的不同。在你的代碼裡,記憶體泄露就是你保留了一個並不再需要的類對象的引用,有時候僅僅一個引用就會阻礙gc對一大堆對象的回收。
我們來過一個實際的例子,Android SDK裡面提供的範常式序Honeycomb Gallery sample app 。它是一個photo gallery程式,用來示範一些新的Honeycomb API的使用。(下載和編譯這些代碼,請看這些 命令 )我們會有意地加入一個記憶體泄露在程式裡邊,然後來示範如何調試它。
想象一下我們想修改程式讓它從網路下載圖片,為了讓它更具備靈活性,我們可以考慮實現一個緩衝,儲存最近查看過的圖片,我們可以對ContentFragment.java做一些小的修改來達到這個目的。在class頂部,我們增加一個新的靜態變數:
private static HashMap<String,Bitmap> sBitmapCache = new HashMap<String,Bitmap>();
這裡是我們儲存緩衝的地方,現在可以修改updateContentAndRecycleBitmap()方法,讓它在下載之前先查看是否資料已經存在,如果不存在就去下載,然後添加資料到緩衝。
void updateContentAndRecycleBitmap (int category, int position) {<br />if (mCurrentActionMode != null) {<br />mCurrentActionMode.finish();<br />}</p><p>// Get the bitmap that needs to be drawn and update the ImageView.</p><p>// Check if the Bitmap is already in the cache<br />String bitmapId = "" + category + "." + position;<br />mBitmap = sBitmapCache.get(bitmapId);</p><p>if (mBitmap == null) {<br />// It's not in the cache, so load the Bitmap and add it to the cache.<br />// DANGER! We add items to this cache without ever removing any.<br />mBitmap = Directory.getCategory(category).getEntry(position).getBitmap(getResources());<br />sBitmapCache.put(bitmapId, mBitmap);<br />}</p><p>((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);<br />}
在這裡故意引入了一個記憶體泄露的問題:我們把圖片加入了緩衝但是從來沒有移除他們,在真實的應用裡,我們可以會用某種方法來限制緩衝的大小。
四、 DDMS檢查heap
Dalvik Debug Monitor Server(DDMS)是主要的Android調試工具之一,也是 ADT Plugin for Eclipse 的一部分,獨立的程式版本也可以在Android SDK的根目錄下的tools/下面找到。關於DDMS更多的資訊,請參考使用DDMS 。
我們來使用DDMS檢查這個應用的heap使用方式,你可以使用下面的兩種方法啟動DDMS:
- from Eclipse: click Window —> Open Perspective —> Other... —> DDMS
- from the command line: run
ddms
(or ./ddms
on Mac/Linux) in the tools/
directory
在左邊的面板,選擇進程com.example.android.hcgallery,然後在工具條上邊點擊Show heap updates按鈕。這個時候切換到DDMS的VM Heap分頁,它會顯示每次gc後heap記憶體的一些基本資料。
如果想查看第一次gc後的資料內容,點擊Cause GC按鈕:
我們可以看到現在的值(Allocated列)是有一些超過8MB。現在滑動相片,這時看到 資料在增大,因為只有僅僅13個相片在程式裡邊,所以泄露的記憶體只有這麼大。在某種程度上來說,這是最壞的一種記憶體泄露,因為我們沒法得到 OutOfMemoryError來提醒我們說現在記憶體溢出了。
五、 產生heap dump
為了使用heap dump來追蹤這個問題,首先要儲存HPROF檔案:
點擊DDMS工具條上面的Dump HPROF檔案按鈕,選擇檔案儲存體位置,然後在運行hprof-conv。在這個例子裡我們使用獨立的MAT版本(版本1.0.1),MAT下載 。
如果使用ADT(它包含DDMS的外掛程式)同時也在eclipse裡面安裝了MAT,點擊“dump HPROF”按鈕將會自動地做轉換(用hprof-conv),同時會在eclipse裡面開啟轉換後的hprof檔案(它其實用MAT開啟)。
六、 MAT分析heap dumps
啟動MAT,然後載入剛才我們產生的HPROF檔案。
MAT是一個強大的工具,講述它所有的特性超出了本文的範圍,所以我只想示範一種你可以用來檢測 泄露的方法:長條圖(Histogram)視圖。它顯示了一個可以排序的類執行個體的列表,內容包括:shallow heap(所有執行個體的記憶體使用量總和),或者retained heap(所有類執行個體被分配的記憶體總和,裡面也包括他們所有引用的對象)。
如果我們按照shallow heap排序,我們可以看到byte[] 執行個體在頂端。自從Android 3.0(Honeycomb),Bitmap的像素資料被儲存在byte數組裡 (之前是被儲存在Dalvik的heap裡),所以基於這個對象的大小來判斷,不用說它一定是我們泄露掉的bitmap。
右擊byte[] 類,然後選擇List Objects —> with incoming references,它會產生一個heap上的所有byte數組的列表,在列表裡我們可以按照Shallow Heap的使用方式來排序。
選擇並展開一個比較大的對象,它將展示從根到這個對象的路徑 —— 就是一條保證對象有效鏈條。注意看,這個就是我們的bitmap緩衝!
MAT 不會明確告訴我們這就是泄露,因為它也不知道這個東西是不是程式還需要的,只有程式員知道。
在這個案例裡面,緩衝使用的大量的記憶體會影響到後面的應用程式,所以我們可以考慮限制緩衝的大小。
七、 MAT 比較兩個heap dumps
調試記憶體泄露時,有時候適時比較2個地方的heap狀態是很有用的。這時你就需要產生2個單獨的HPROF檔案(不要忘了轉換格式),下面是一些關於如何在MAT裡比較2個heap dumps的內容(有一點複雜):
- 第一個HPROF 檔案(using File —> Open Heap Dump )
- 開啟 Histogram view
- 在Navigation History view裡 (如果看不到就從Window —> Navigation History找 ), 右擊histogram然後選擇Add to Compare Basket
- 開啟第二個HPROF 檔案然後重做步驟2和3
- 切換到Compare Basket view,然後點擊Compare the Results (視圖右上方的紅色"!"表徵圖)。
八、 總結
這本篇文章裡面,我展示了Allocation Tracker 和 heap dumps是如何給你一種對程式記憶體使用量的感性認識。我也展示了MAT可以協助追逐我們程式裡面的記憶體泄露問題。
MAT是一個強大的工具,我也僅僅觸碰了一些皮毛,如果你想學習更多內容,我建議讀 一些下面的文章:
- Eclipse MAT project的官方部落格:Memory Analyzer Blog 或 Blog.sixxs.org
- Markus Kohler 的 有關Java效能的部落格:Java Performance blog 或 blog.sixxs.org
說明: 上面兩個部落格網址,可能被“牆”了,需要通過VPN 或 IPv6 + sixxs.org代理訪問