Android 內存優化以及內存泄漏分析工具


假設有一個單例的ListenerManager, 可以add / remove Listener, 有一個Activity, 實現了該listener, 且這個Activity中持有大對象BigObject, BigObject中包含一個大的字符串數組和一個Bitmap List.

代碼片段如下:

ListenerManager

 1 public class ListenerManager {
 2 
 3     private static ListenerManager sInstance;
 4     private ListenerManager() {}
 5 
 6     private List<SampleListener> listeners = new ArrayList<>();
 7 
 8     public static ListenerManager getInstance() {
 9         if (sInstance == null) {
10             sInstance = new ListenerManager();
11         }
12 
13         return sInstance;
14     }
15 
16     public void addListener(SampleListener listener) {
17         listeners.add(listener);
18     }
19 
20     public void removeListener(SampleListener listener) {
21         listeners.remove(listener);
22     }
23 }

MemoryLeakActivity

 1 public class MemoryLeakActivity extends AppCompatActivity implements SampleListener {
 2 
 3     private BigObject mBigObject = new BigObject();
 4 
 5     @Override
 6     protected void onCreate(Bundle savedInstanceState) {
 7         super.onCreate(savedInstanceState);
 8         setContentView(R.layout.activity_memory_leak);
 9 
10         ListenerManager.getInstance().addListener(this);
11     }
12 
13     @Override
14     public void doSomething() {
15 
16     }
17 }

BigObject

 1 public class BigObject {
 2 
 3     private ArrayList<Bitmap> bitmaps = new ArrayList<>();
 4     private String[] values = new String[1000];
 5 
 6     public BigObject() {
 7 
 8         for (int i = 0; i < 20; i++) {
 9             bitmaps.add(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
10         }
11 
12         for (int i = 0; i < 1000; i++) {
13             values[i] = "value:" + i;
14         }
15     }

1. Memory Monitor

Memory Monitor 是 Android Studio內置的, 官方的內存監測工具. 圖形化的展示當前應用的內存狀態, 包括已分配內存, 空閑內存, 內存實時動態等.

  • 圖形區域:

    • 橫向時間軸, 內存檢測時間, 跟隨滾動.
    • 縱向內存軸, 內存使用量, 根據應用使用動態分配.
    • 藍色區域表示當前已分配使用的內存量.
    • 灰色區域表示剩余可使用的內存量.
    • 紅色圈圈指示的是系統GC事件(內存有一定量的回收).
  • 工具欄:

    • ① GC按鈕, 點擊執行一次GC操作.
    • ② Dump Java Heap按鈕, 點擊會在該調試工程的captures目錄生成一個類似這樣"com.anly.githubapp_2016.09.21_23.42.hprof"命名的hprof文件, 並打開Android Studio的HPROF Viewer顯示該文件內容.
    • ③ Allocation Traking按鈕, 點擊一次開始, 再次點擊結束, 同樣會在captrures目錄生成一個文件, 類似"com.anly.githubapp_2016.09.21_23.48.alloc", alloc后綴的文件, 並打開Allocation Tracker視圖展示該文件內容.

1.1 查看Memory使用, 並導出hprof文件

Memory Monitor通過Dump Java Heap可以生成一個hprof的文件, 這個文件是Android特定的Heap和CPU分析文件, 記錄了這段時間內的Java Heap變化.

關於Java Heap

由Java Heap文件可以看到如下數據:

  • 按類型顯示對象申請的內存快照(內存大小);
  • 每次自動或手動觸發GC時的樣本數據;
  • 協助定位可能發生的內存泄露點:
    • 所有已經被destroyed的activity, 還可以從GC Root訪問到.
    • 重復的String實例.

啟動我們要檢測的Activity(MemoryLeakActivity), 然后退出, 在monitor中查看內存變化:

1.2 在HPROF Viewer界面, 開始分析

可點擊選擇Heap類型, Heap類型分為:

  • App Heap -- 當前App使用的Heap
  • Image Heap -- 磁盤上當前App的內存映射拷貝
  • Zygote Heap -- Zygote進程Heap(每個App進程都是從Zygote孵化出來的, 這部分基本是framework中的通用的類的Heap)

第一步

點擊"Analyzer Tasks"視圖中的啟動按鈕, 啟動分析

分析任務包括:

  • 檢測泄露的Activity
  • 查找重復的String實例

第二步

查看"Analysis Result"中的分析結果, 點擊"Leaked Activityes"中的具體實例, 該實例的引用關系將會展示在"Reference Tree"視圖中.

第三步

根據"Reference Tree"視圖中的引用關系找到是誰讓這個leak的activity活着的, 也就是誰Dominate這個activity對象.

此例中, 比較簡單, 可以很清晰看到是ListenerManager的靜態單例sInstance最終支配了MemoryLeakActivity. sIntance連接到GC Roots, 故而導致MemoryLeakActivity GC Roots可達, 無法被回收.

1.3 使用Heap Viewer查看內存消耗

上述步驟, 可以讓我們快速定位可能的內存泄露. 當然, 內存問題, 除了內存泄露, 還有內存消耗過大. 我們可以在Heap Viewer中查看分析內存的消耗點, 如下:

4, MAT

Eclipse MAT是一個快速且功能豐富的Java Heap分析工具, 可以幫助我們尋找內存泄露, 減少內存消耗.

MAT可以分析程序(成千上萬的對象產生過程中)生成的Heap dumps文件, 它會快速計算出對象的Retained Size, 來展示是哪些對象沒有被GC, 自動生成內存泄露疑點的報告.

MAT. 相對與Android Studio中的Memory Monitor, HPROF工具來說, MAT的使用顯得更加生澀, 難以理解.

關於MAT的幫助文檔, 個人翻譯了一份, 需要的同學戳這里.

當然, 如果我們想對內存的使用相關知識了解得更多, 還是有必要了解下MAT的...
下面我們以幾個角度來了解下MAT的基本使用:

再次:
Android Studio導出的hprof文件需要轉換下才可以在MAT中使用.

終端輸入: 
1 $ hprof-conv com.anly.samples_2016.10.31_15.07.hprof mat.hprof

4.1 Histogram視圖定位內存消耗

MAT中很多視圖的第一行, 都可以輸入正則, 來匹配我們關注的對象實例.

4.2 Dominate Tree視圖查看支配關系

4.3 使用OQL查詢相關對象

對於Android App開發來說, 大部分的內存問題都跟四大組件, 尤其是Activity相關, 故而我們會想查出所有Activity實例的內存占用情況, 可以使用OQL來查詢:

具體OQL語法看這里.

4.4 GC路徑定位問題

上面幾個視圖都可以讓我們很快速的找到內存的消耗點, 接下來我們要分析的就是為何這些個大對象沒有被回收.

根據第一彈:GC那些事兒所言, 對象沒有被回收是因為他有到GC Roots的可達路徑. 那么我們就來分析下這條路徑(Path to GC Roots), 看看是誰在這條路中"搭橋".

如下, 進入該對象的"path2gc"視圖:

同樣, 與HPROF Analyzer異曲同工, 找出了是ListenerManager的靜態實例導致了MemoryLeakActivity無法回收.

5. LeakCanary讓內存泄露無處可藏

大道至簡, 程序員都應該"懶", 故而我們都希望有更方便快捷的方式讓我們發現內存泄露. 偉大的square發揮了這一優良傳統, LeakCanary面世.

5.1 加入LeakCanary

app的build.gradle中加入:

1 dependencies {
2    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
3    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
4    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
5 }

Application中加入:

1 public class SampleApplication extends Application {
2 
3     @Override
4     public void onCreate() {
5         super.onCreate();
6 
7         LeakCanary.install(this);
8     }
9 }

4.2 操作要檢測的界面, 查看結果

當發生可疑內存泄露時, 會在桌面生成一個"Leaks"的圖標, 點擊進去可以看到內存泄露的疑點報告:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM