假設有一個單例的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視圖定位內存消耗

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"的圖標, 點擊進去可以看到內存泄露的疑點報告:

