LeakCanary


一、簡介

使用MAT來分析內存問題,有一些門檻,會有一些難度,並且效率也不是很高,對於一個內存泄漏問題,可能要進行多次排查和對比才能找到問題原因。 為了能夠簡單迅速的發現內存泄漏,Square公司基於MAT開源了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 LeakApplication extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) {//1 // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this); } } 

如果當前的進程是用來給LeakCanary 進行堆分析的則return,否則會執行LeakCanary的install方法。這樣我們就可以使用LeakCanary了,如果檢測到某個Activity 有內存泄露,LeakCanary 就會給出提示。

 
 

例子代碼只能夠檢測Activity的內存泄漏,當然還存在其他類的內存泄漏,這時我們就需要使用RefWatcher來進行監控。改寫Application,如下所示:

public class MyApplication extends Application { private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher= setupLeakCanary(); } private RefWatcher setupLeakCanary() { if (LeakCanary.isInAnalyzerProcess(this)) { return RefWatcher.DISABLED; } return LeakCanary.install(this); } public static RefWatcher getRefWatcher(Context context) { MyApplication leakApplication = (MyApplication) context.getApplicationContext(); return leakApplication.refWatcher; } } 

install方法會返回RefWatcher用來監控對象,LeakApplication中還要提供getRefWatcher靜態方法來返回全局RefWatcher。

最后為了舉例,我們在一段存在內存泄漏的代碼中引入LeakCanary監控,如下所示。

public class OtherActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LeakThread leakThread = new LeakThread(); leakThread.start(); } class LeakThread extends Thread { @Override public void run() { try { CommonUtils.getInstance(OtherActivity.this); Thread.sleep(6 * 60 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = MyApplication.getRefWatcher(this);//1 refWatcher.watch(this); } } 

MainActivity存在內存泄漏,原因就是非靜態內部類LeakThread持有外部類MainActivity的引用,LeakThread中做了耗時操作,導致MainActivity無法被釋放。

它用於自動監控Activity執行onDestroy方法之后是否發生內存泄露,當前此例onDestroy加是多余的,這里只是為了方便舉例,如果想要監控Fragment,在Fragment中添加如上的onDestroy方法是有用的。

運行程序,這時會在界面生成一個名為Leaks的應用圖標。接下來不斷的切換橫豎屏,這時會閃出一個提示框,提示內容為:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,內存泄漏信息就會通過Notification展示出來。

提示的notification:

 
 

使用小結
如果只關注activity的內存泄漏,那么在Application中onCreate加入LeakCanary.install(this);就OK了,

如果還關注fragment的泄漏情況,那么Application加上RefWatcher,然后在對應fragment頁面中onDestroy中加入:

RefWatcher refWatcher = MyApplication.getRefWatcher(this); refWatcher.watch(this);

三、使用踩坑

在剛開始使用LeakCanary的時候,遇到了幾個問題導致有內存泄漏發生時LeakCanary不發生通知,這里和大家分享一下。

1、 你的應用需要有寫SD權限,因為LeakCanary需要生成hprof文件,保存在SD卡里面,因此你的應用要先申請權限

四、原理介紹

4.1 觸發檢測

每次當Activity/Fragment執行完onDestroy生命周期,LeakCanary就會獲取到這個Activity/Fragment,然后初始化RefWatcher對它進行分析,查看是否存在內存泄漏。

4.2 判斷是否存在內存泄漏

通過WeakReference + ReferenceQueue來判斷對象是否被系統GC回收。

軟引用和弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用或弱引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用或弱引用加入到與之關聯的引用隊列中。

首先嘗試着從ReferenceQueue中獲取待分析對象:如果不為空,那么說明正在被系統回收;如果直接就返回DONE,說明已經被系統回收了;如果沒有被系統回收,可能存在內存泄漏,手動觸發系統GC,然后再嘗試移除待分析對象,如果還存在,說明存在內存泄漏。

4.3 分析內存泄漏

確定有內存泄漏后,調用heapDumper.dumpHeap()生成.hprof文件目錄。HAHA 是一個由 square 開源的 Android 堆分析庫,分析 hprof 文件生成Snapshot對象。Snapshot用以查詢對象的最短引用鏈,找到最短引用鏈后,定位問題,排查代碼將會事半功倍。

 
 

ensureGone 方法

我現在講ensureGone方法的完整代碼貼出來,我們逐行分析:

 ...... // 移除所有弱引用可達對象,后面細講 removeWeaklyReachableReferences(); ......  // 上面執行 removeWeaklyReachableReferences 方法,判斷是不是監視對象已經被回收了,如果被回收了,那么說明沒有發生內存泄漏,直接結束 if (gone(reference)) { return DONE; }
// 手動觸發一次 GC 垃圾回收 gcTrigger.runGc();
// 再次移除所有弱引用可達對象 removeWeaklyReachableReferences();
// 如果對象沒有被回收 if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); // 使用 Debug 類 dump 當前堆內存中對象使用情況 File heapDumpFile = heapDumper.dumpHeap(); // dumpHeap 失敗的話,會走重試機制 if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); // 將hprof文件、key等屬性構造一個 HeapDump 對象 HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key) .referenceName(reference.name) .watchDurationMs(watchDurationMs) .gcDurationMs(gcDurationMs) .heapDumpDurationMs(heapDumpDurationMs) .build(); // heapdumpListener 分析 heapDump 對象 heapdumpListener.analyze(heapDump); } return DONE; } 復制代碼

看完上述代碼,基本把檢測泄漏的大致過程走了一遍,下面我們來看一些具體的細節。

(1). removeWeaklyReachableReferences 方法

removeWeaklyReachableReferences 移除所有弱引用可達對象是怎么工作的?

private void removeWeaklyReachableReferences() { KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } } 

還記得我們在 refWatcher.watch 方法保存了當前監視對象的 ref.key 了么,如果這個對象被回收了,那么對應的弱引用對象會在回收時被添加到queue中,通過 poll 操作就可以取出這個弱引用,這時候我們從retainedKeys中移除這個 key, 代表這個對象已經被正常回收,不需要再被監視了。

(2).那么現在來看,判斷這個對象是否被回收就比較簡單了?
private boolean gone(KeyedWeakReference reference) { // retainedKeys 中不包含 reference.key 的話,就代表這個對象已經被回收了 return !retainedKeys.contains(reference.key); } 

整體流程如下:

 
 

詳細原理分析可以參考:LeakCanary原理分析

 

附:

LeakCanary 內存泄漏原理完全解析

LeakCanary,30分鍾從入門到精通


免責聲明!

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



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