1. 程式人生 > >Android應用程式的記憶體分析(翻譯)(轉)

Android應用程式的記憶體分析(翻譯)(轉)

作者:Tim Bray

Dalvik虛擬機器支援垃圾收集,但是這不意味著你可以不用關心記憶體管理。你應該格外注意移動裝置的記憶體使用,在上面記憶體空間是受到限制的。在這篇 文章裡面,我們來看看Android SDK裡面的一些記憶體剖析工具(profiling tools)是如何幫助我們修整應用程式的記憶體使用。

一些記憶體使用問題是很明顯的,例如,如果在每次使用者觸控式螢幕幕的時候應用程式有記憶體洩露,將會有可能觸發OutOfMemoryError,最終程式崩潰。另外一些問題卻很微妙,也許只是降低應用程式和整個系統的效能(當高頻率和長時間地執行垃圾收集器的時候)。

必要的工具:

Android SDK提供了2個主要的剖析應用程式記憶體使用情況的工具:DDMS裡的一個分頁Allocation Tracker和heap dumps。Allocation Tracker是很有用的,特別是當你想得到程式在一定的時間裡記憶體的分配情況的一種感性認識的時候。但是它不能給你任何關於程式heap總體情況的任何 資訊。關於Allocation Tracker的更多資訊,請看文章

Tracking Memory Allocations 。文章剩下的內容將把重點放在heap dumps,它是更強大的記憶體分析工具。


一個heap dump就是一個程式heap的快照,它儲存為一種叫做HPROF的二進位制格式。Dalvik用的也是類似的格式,但是不完全一樣。這裡是Java的HPROF工具 。有很多方法去生成一個執行時應用程式的heap dump。其中一種就是使用在DDMS裡邊的Dump HPROF file按鈕。如果想產生更精確的dump資料,可以在程式中使用android.os.Debug.dumpHprofData() 方法。

分析heap dump,你可以使用一些標準的工具比如

jhat 或者Eclipse Memory Analyzer(MAT) 。不過,首先你需要把.hprof檔案從Dalvik格式轉換成J2SE HPROF格式。你可以使用Android SDK提供的hprof-conv工具。例如:

  1. hprof-conv dump.hprof converted-dump.hprof

除錯一個記憶體洩露例項:


在Dalvik執行時裡邊,程式設計師不能顯式地分配和釋放記憶體,所以這裡的記憶體洩露跟c和c++裡面的不同。在你的程式碼裡邊,記憶體洩露就是你保留了一個並不再需要的類物件的引用。有時候僅僅一個引用就會阻礙gc對一大堆物件的回收。

我們來過一個實際的例子,Android SDK裡面提供的範例程式

Honeycomb Gallery sample app 。它是一個photo gallery程式,用來演示一些新的Honeycomb API的使用。(下載和編譯這些程式碼,請看這些命令 。)我們會有意地加入一個記憶體洩露在程式裡邊,然後來演示如何除錯它。


想象一下我們想修改程式讓它從網路下載圖片。為了讓它更具備靈活性,我們可以考慮實現一個快取,儲存最近檢視過的圖片。我們可以對ContentFragment.java做一些小的修改來達到這個目的。在class頂部,我們增加一個新的靜態變數:

  1. privatestatic HashMap<String,Bitmap> sBitmapCache = new HashMap<String,Bitmap>();

這裡就是我們儲存快取的地方。現在我們可以修改updateContentAndRecycleBitmap()方法,讓它在下載之前先檢視是否資料已經存在,如果不存在就去下載,然後新增資料到快取。

  1. void updateContentAndRecycleBitmap(int category, int position) {
  2. if (mCurrentActionMode != null) {
  3. mCurrentActionMode.finish();
  4. }
  5. // Get the bitmap that needs to be drawn and update the ImageView.
  6. // Check if the Bitmap is already in the cache
  7. String bitmapId = "" + category + "." + position;
  8. mBitmap = sBitmapCache.get(bitmapId);
  9. if (mBitmap == null) {
  10. // It's not in the cache, so load the Bitmap and add it to the cache.
  11. // DANGER! We add items to this cache without ever removing any.
  12. mBitmap = Directory.getCategory(category).getEntry(position)
  13. .getBitmap(getResources());
  14. sBitmapCache.put(bitmapId, mBitmap);
  15. }
  16. ((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);
  17. }

我已經在這裡故意引入了一個記憶體洩露的問題:我們把圖片加入了快取但是從來沒有移除他們。在真實的應用裡,我們可以會用某種方法來限制快取的大小。

在DDMS裡檢查heap的使用情況

Dalvik Debug Monitor Server(DDMS)是主要的Android除錯工具之一,也是ADT Eclipse plug-in 的一部分,獨立的程式版本也可以在Android SDK的根目錄下的tools/下面找到。關於DDMS更多的資訊,請參考使用DDMS

我們來使用DDMS檢查這個應用的heap使用情況。你可以使用下面的兩種方法啟動DDMS:

  • from Eclipse: click Window > Open Perspective > Other... > DDMS
  • or 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來追蹤這個問題。點選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[]例項在頂端。自從Android3.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的內容(有一點複雜):

  1. 第一個HPROF 檔案(using File > Open Heap Dump ).
  2. 開啟 Histogram view.
  3. 在Navigation History view裡 (如果看不到就從Window > Navigation History找 ), 右擊histogram 然後選擇Add to Compare Basket .
  4. 開啟第二個HPROF 檔案然後重做步驟2和3.
  5. 切換到Compare Basket view, 然後點選Compare the Results (檢視右上角的紅色"!"圖示)。

總結

這本篇文章裡面,我展示了Allocation Tracker和heap dumps是如何給你一種對程式記憶體使用的感性認識。我也展示了Eclipse Memory Analyzer(MAT)可以幫助追逐我們程式裡面的記憶體洩露問題。MAT是一個強大的工具,我也僅僅觸碰了一些皮毛,如果你想學習更多內容,我建議讀 一些下面的文章: