引言
前文記憶體分析工具集中介紹了一系列的記憶體分析工具及其基本使用, 諸如Memory Monitor, HPROF Viewer, MAT等等. 實際上了解了工具的使用, 我們就已經掌握瞭如何分析記憶體問題了.
為了能對工具的使用更加深入, 本篇將一個程式碼片段為例, 從時序的角度講解下如何使用這些工具來分析一個記憶體洩露.
系列文:
1.GC那些事兒
2.Android的記憶體管理
3.記憶體分析工具
4.記憶體洩露例項分析
1, 例子
假設有一個單例的ListenerManager, 可以add / remove Listener, 有一個Activity, 實現了該listener, 且這個Activity中持有大物件BigObject, BigObject中包含一個大的字串陣列和一個Bitmap List.
程式碼片段如下:
ListenerManager
public class ListenerManager {
private static ListenerManager sInstance;
private ListenerManager() {}
private List<SampleListener> listeners = new ArrayList<>();
public static ListenerManager getInstance() {
if (sInstance == null) {
sInstance = new ListenerManager();
}
return sInstance;
}
public void addListener(SampleListener listener) {
listeners.add(listener);
}
public void removeListener(SampleListener listener) {
listeners.remove(listener);
}
}複製程式碼
MemoryLeakActivity
public class MemoryLeakActivity extends AppCompatActivity implements SampleListener {
private BigObject mBigObject = new BigObject();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_leak);
ListenerManager.getInstance().addListener(this);
}
@Override
public void doSomething() {
}
}複製程式碼
具體程式碼參看Github.
2, 使用Android Studio的自帶工具來分析
根據前文的工具介紹, Android Studio自帶了Memory Monitor, HPROF Viewer & Analyzer來分析記憶體使用及記憶體問題.
2.1 檢視Memory使用, 並匯出hprof檔案
啟動我們要檢測的Activity(MemoryLeakActivity), 然後退出, 在monitor中檢視記憶體變化:
2.2 在HPROF Viewer介面, 開始分析
第一步
點選"Analyzer Tasks"檢視中的啟動按鈕, 啟動分析
第二步
檢視"Analysis Result"中的分析結果, 點選"Leaked Activityes"中的具體例項, 該例項的引用關係將會展示在"Reference Tree"檢視中.
第三步
根據"Reference Tree"檢視中的引用關係找到是誰讓這個leak的activity活著的, 也就是誰Dominate這個activity物件.
此例中, 比較簡單, 可以很清晰看到是ListenerManager的靜態單例sInstance最終支配了MemoryLeakActivity. sIntance連線到GC Roots, 故而導致MemoryLeakActivity GC Roots可達, 無法被回收.
2.3 使用Heap Viewer檢視記憶體消耗
上述步驟, 可以讓我們快速定位可能的記憶體洩露. 當然, 記憶體問題, 除了記憶體洩露, 還有記憶體消耗過大. 我們可以在Heap Viewer中檢視分析記憶體的消耗點, 如下:
3, MAT讓我們看的更多
就單純的分析Android App的記憶體使用和記憶體洩露來說, 個人覺得Android Studio自帶的工具已經足夠好了, 而且再持續變得更好, 也更便於Android的開發人員去理解. 故而其實一開始在Android效能分析工具一文中, 我就沒有詳細去提MAT. 相對與Android Studio中的Memory Monitor, HPROF工具來說, MAT的使用顯得更加生澀, 難以理解.
關於MAT的幫助文件, 個人翻譯了一份, 需要的同學戳這裡.
當然, 如果我們想對記憶體的使用相關知識瞭解得更多, 還是有必要了解下MAT的...
下面我們以幾個角度來了解下MAT的基本使用:
再次:
Android Studio匯出的hprof檔案需要轉換下才可以在MAT中使用.
$ hprof-conv com.anly.samples_2016.10.31_15.07.hprof mat.hprof複製程式碼
3.1 Histogram檢視定位記憶體消耗
MAT中很多檢視的第一行, 都可以輸入正則, 來匹配我們關注的物件例項.
3.2 Dominate Tree檢視檢視支配關係
3.3 使用OQL查詢相關物件
對於Android App開發來說, 大部分的記憶體問題都跟四大元件, 尤其是Activity相關, 故而我們會想查出所有Activity例項的記憶體佔用情況, 可以使用OQL來查詢:
具體OQL語法看這裡.
3.4 GC路徑定位問題
上面幾個檢視都可以讓我們很快速的找到記憶體的消耗點, 接下來我們要分析的就是為何這些個大物件沒有被回收.
根據第一彈:GC那些事兒所言, 物件沒有被回收是因為他有到GC Roots的可達路徑. 那麼我們就來分析下這條路徑(Path to GC Roots), 看看是誰在這條路中"搭橋".
如下, 進入該物件的"path2gc"檢視:
同樣, 與HPROF Analyzer異曲同工, 找出了是ListenerManager的靜態例項導致了MemoryLeakActivity無法回收.
4, LeakCanary讓記憶體洩露無處可藏
大道至簡, 程式設計師都應該"懶", 故而我們都希望有更方便快捷的方式讓我們發現記憶體洩露. 偉大的square發揮了這一優良傳統, LeakCanary面世.
4.1 加入LeakCanary
app的build.gradle中加入:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}複製程式碼
Application中加入:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}複製程式碼
4.2 操作要檢測的介面, 檢視結果
當發生可疑記憶體洩露時, 會在桌面生成一個"Leaks"的圖示, 點選進去可以看到記憶體洩露的疑點報告:
可以看到, 結果與前二者的分析結果"驚人"一致, 不一致就出事兒了, :)
足夠方便且直觀吧, 趕快用起來吧.
當然, 記憶體問題不僅僅是記憶體洩露, 還有記憶體佔用過多等, 這時我們就需要藉助前兩種工具了.
結語
綜上, 建議LeakCanary整合作為App的必選項, 大多數情況下我們可以用LeakCanary結合Android Studio自帶的工具分析記憶體問題.
如果有精力, 還是建議深入瞭解下MAT, 能讓我們更深入瞭解GC的機制, 相關概念, MAT還有很多更高階的功能值得我們探索, 例如Heap比較等.
其實, 記憶體問題的分析, 無外乎分析物件的記憶體佔用(Retained Size), 找出Retained Size大的物件, 找到其直接支配(Immediate Dominator), 跟蹤其GC可達路徑(Path to GC Roots), 從而找到是誰讓這個大物件活著. 找到問題癥結, 對症下藥.