[摘要:應用 參考我以前寫的《Android 內存泄露對象應用》 監控 Activity 泄漏 咱們經常把 Activity 當作爲 Context 工具應用,正在分歧場所由種種工具援用 Activity。以是,] java
http://www.jianshu.com/p/0049e9b344b0node
參考我以前寫的《Android 內存泄漏工具使用》android
咱們常常把 Activity 看成爲 Context 對象使用,在不一樣場合由各類對象引用 Activity。因此,Activity 泄漏是一個重要的須要檢查的內存泄漏之一。算法
public class ExampleApplication extends Application { public static RefWatcher getRefWatcher(Context context) { ExampleApplication application = (ExampleApplication) context.getApplicationContext(); return application.refWatcher; } private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install(this); } }
LeakCanary.install() 返回一個配置好了的 RefWatcher 實例。它同時安裝了 ActivityRefWatcher 來監控 Activity 泄漏。即當 Activity.onDestroy() 被調用以後,若是這個 Activity 沒有被銷燬,logcat 就會打印出信息告訴你內存泄漏發生了。api
leakInfo:In com.example.leakcanary:1.0:1. * com.example.leakcanary.MainActivity has leaked: * GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1') * references com.example.leakcanary.MainActivity$2.this$0 (anonymous subclass of android.os.AsyncTask) * leaks com.example.leakcanary.MainActivity instance *
LeakCanary 自動檢測 Activity 泄漏只支持 Android ICS 以上版本。由於 Application.registerActivityLifecycleCallbacks() 是在 API 14 引入的。若是要在 ICS 以前監測 Activity 泄漏,能夠重載 Activity.onDestroy() 方法,而後在這個方法裏調用 RefWatcher.watch(this) 來實現。緩存
public abstract class BaseFragment extends Fragment { @Override public void onDestroy() { super.onDestroy(); RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); refWatcher.watch(this); } }
當 Fragment.onDestroy() 被調用以後,若是這個 fragment 實例沒有被銷燬,那麼就會從 logcat 裏看到相應的泄漏信息。app
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); refWatcher.watch(someObjNeedGced);
當 someObjNeedGced 還在內存中時,就會在 logcat 裏看到內存泄漏的提示。dom
1. RefWatcher.watch() 建立一個 KeyedWeakReference 到要被監控的對象。
2. 而後在後臺線程檢查引用是否被清除,若是沒有,調用GC。
3. 若是引用仍是未被清除,把 heap 內存 dump 到 APP 對應的文件系統中的一個 .hprof 文件中。
4. 在另一個進程中的 HeapAnalyzerService 有一個 HeapAnalyzer 使用 另外一個開源庫HAHA 解析這個hprof文件。(MAT也能夠解析hprof,可是是圖形化的形式,HAHA徹底是非UI形式解析)
5. 得益於惟一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內存泄露。
6. HeapAnalyzer 計算 到 GC roots 的最短強引用路徑,並肯定是不是泄露。若是是的話,創建致使泄露的引用鏈。
7. 引用鏈傳遞到 APP 進程中的 DisplayLeakService, 並以通知的形式展現出來。ide
如今就來經過代碼來說解吧
首先,Application的onCreate方法中調用LeakCanary.install(this);
作初始化工做
LeakCanary.java工具
public static RefWatcher install(Application application) { return install(application, DisplayLeakService.class, AndroidExcludedRefs.createAppDefaults().build()); } public static RefWatcher install(Application application, Class<? extends AbstractAnalysisResultService> listenerServiceClass, ExcludedRefs excludedRefs) { //首先判斷當前應用程序進程是否在HeapAnalyzerService所在的進程,若是是直接return,HeapAnalyzerService所在的進程是HAHA庫專門解析hprof文件的進程 if (isInAnalyzerProcess(application)) { return RefWatcher.DISABLED; } enableDisplayLeakActivity(application); HeapDump.Listener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass); //listenerServiceClass就是上面的DisplayLeakService.class,用來顯示內存泄漏信息用的 RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs); //構造RefWatcher類對象 ActivityRefWatcher.installOnIcsPlus(application, refWatcher); //調用ActivityRefWatcher的方法 return refWatcher; } //ActivityRefWatcher類,專門針對activity,因此在api14以上的onDestroy中不用監控,自動回調 @TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher { private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { .............. @Override public void onActivityDestroyed(Activity activity) { ActivityRefWatcher.this.onActivityDestroyed(activity); //當activity退出執行了onDestory方法的時候就會執行該onActivityDestroyed方法 } }; private final Application application; private final RefWatcher refWatcher; void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); //調用watch方法 } public void watchActivities() { // Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); //api14才引入的方法,任何activity生命週期的變化都會回調給lifecycleCallbacks } public void stopWatchingActivities() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); } }
excludedRefs
/* References that should be ignored when analyzing this heap dump. /
SDK致使的內存泄露,隨着時間的推移,不少SDK 和廠商 ROM 中的內存泄露問題已經被儘快修復了。可是,當這樣的問題發生時,通常的開發者能作的事情頗有限。LeakCanary有一個已知問題的忽略列表,AndroidExcludedRefs.java
中能夠查看到
就是說若是發現AndroidExcludedRefs.java
類中維護的列表類的內存泄漏,那麼在DisplayLeakService並不會顯示出來,同時HeapAnalyzer在計算到GC roots的最短強引用路徑,也會忽略這些類
下面具體分析。
此時初始化完畢,若是退出一個activity那麼就會執行refWatcher.watch(activity);
RefWatcher.java
public void watch(Object watchedReference, String referenceName) { checkNotNull(watchedReference, "watchedReference"); checkNotNull(referenceName, "referenceName"); if (debuggerControl.isDebuggerAttached()) { //連上數據線就此時就返回true了 return; } final long watchStartNanoTime = System.nanoTime(); String key = UUID.randomUUID().toString(); //惟一的key retainedKeys.add(key); //key保存在retainedKeys集合 final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); //虛引用KeyedWeakReference watchExecutor.execute(new Runnable() { //watchExecutor就不說了,自定義的一個Executor @Override public void run() { ensureGone(reference, watchStartNanoTime); //關鍵ensureGone方法 } }); } void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); removeWeaklyReachableReferences(); if (gone(reference) || debuggerControl.isDebuggerAttached()) { //判斷reference是否被回收了已經爲null,同時是否插了USB線,調試模式下,不去檢查內存泄漏 return; } gcTrigger.runGc(); //觸發GC removeWeaklyReachableReferences(); if (!gone(reference)) { //哈,此時說明強制發生GC,該引用還在,那麼就沒被回收咯,可能發生內存泄漏,因此才能繼續往下走 long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = heapDumper.dumpHeap(); //關鍵,生成dropf文件 if (heapDumpFile == HeapDumper.NO_DUMP) { //NO_DUMP類型,木有生成dropf吧,直接return // Could not dump the heap, abort. return; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); //heapdumpListener就是上面初始化的ServiceHeapDumpListener } } private boolean gone(KeyedWeakReference reference) { //當referent被回收了,因為removeWeaklyReachableReferences方法因此該key被移出了,contains返回false,gone返回true //當referent沒被回收了,因為key還在retainedKeys中,contains返回true,gone返回false return !retainedKeys.contains(reference.key); } private void removeWeaklyReachableReferences() { // WeakReferences are enqueued as soon as the object to which they point to becomes weakly // reachable. This is before finalization or garbage collection has actually happened. KeyedWeakReference ref; //ReferenceQueue的poll方法,當referent被回收了那麼poll方法返回不為null,沒被GC回收才為null while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); //當referent被回收了,那麼從retainedKeys移出該key } }
先稍微看下KeyedWeakReference的定義,繼承自WeakReference
final class KeyedWeakReference extends WeakReference<Object> { public final String key; public final String name; KeyedWeakReference(Object referent, String key, String name, ReferenceQueue<Object> referenceQueue) { super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue")); this.key = checkNotNull(key, "key"); this.name = checkNotNull(name, "name"); } }
測試一下
public static void main(String[] args) { // TODO Auto-generated method stub WeakReference w = new WeakReference(new String("haha"), queue); System.gc(); if (w.get() == null) { System.out.println("w get is null"); } else { System.out.println("w get is not null"); } WeakReference temp= (WeakReference) queue.poll(); if(temp != null) { // ReferenceQueue的poll方法 System.out.println("queue.poll is not null"); }else{ System.out.println("queue.poll is null"); } }
打印
w get is null
queue.poll is not null
註釋掉System.gc();
此時打印
w get is not null
queue.poll is null
然後看看heapDumper.dumpHeap()
怎麼生成hprof文件,而後再看heapdumpListener.analyze流程
AndroidHeapDumper.java
@Override public File dumpHeap() { if (!leakDirectoryProvider.isLeakStorageWritable()) { CanaryLog.d("Could not write to leak storage to dump heap."); leakDirectoryProvider.requestWritePermission(); return NO_DUMP; } File heapDumpFile = getHeapDumpFile(); // Atomic way to check for existence & create the file if it doesn't exist. // Prevents several processes in the same app to attempt a heapdump at the same time. boolean fileCreated; try { fileCreated = heapDumpFile.createNewFile(); } catch (IOException e) { cleanup(); CanaryLog.d(e, "Could not check if heap dump file exists"); return NO_DUMP; } if (!fileCreated) { CanaryLog.d("Could not dump heap, previous analysis still is in progress."); // Heap analysis in progress, let's not put too much pressure on the device. return NO_DUMP; } FutureResult<Toast> waitingForToast = new FutureResult<>(); //在生成heap dump文件的時候會彈出一個toast提示界面 showToast(waitingForToast); if (!waitingForToast.wait(5, SECONDS)) { CanaryLog.d("Did not dump heap, too much time waiting for Toast."); return NO_DUMP; } Toast toast = waitingForToast.get(); try { Debug.dumpHprofData(heapDumpFile.getAbsolutePath()); //Debug.dumpHprofData方法生成hprof並寫入該file,其實DDMS獲取hprof文件應該也是調用該方法的 cancelToast(toast); return heapDumpFile; } catch (Exception e) { cleanup(); CanaryLog.d(e, "Could not perform heap dump"); // Abort heap dump return NO_DUMP; } }
hprof文件默認放在/sdcard/Download/…/目錄下,因此若是要使用MAT分析的話,能夠到該目錄下尋找,該hprof不能直接被MAT查看,須要android提供的hprof-conv轉換
生成了hprof文件以後,執行heapdumpListener.analyze流程,主要就是經過另一個進程中的HeapAnalyzerService有一個HeapAnalyzer(內部實際調用開源庫HAHA)解析這個hprof文件,而後若是須要就顯示在傳遞到APP進程中的DisplayLeakService,並以通知的形式展現出來,代碼以下
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); public final class HeapAnalyzerService extends IntentService { public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) { //靜態方法 Intent intent = new Intent(context, HeapAnalyzerService.class); //啓動本服務,此時HeapAnalyzerService運行在leakcanary獨立進程中 intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName()); //HeapAnalyzerService intent.putExtra(HEAPDUMP_EXTRA, heapDump); //hropf文件 context.startService(intent); } @Override protected void onHandleIntent(Intent intent) { if (intent == null) { CanaryLog.d("HeapAnalyzerService received a null intent, ignoring."); return; } String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA); //DisplayLeakService HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA); //hropf文件 HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs); //HeapAnalyzer.checkForLeak解析這個hprof文件完畢,那麼發送給app進程的HeapAnalyzerService顯示通知 AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey); AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result); } } <service android:name="com.squareup.leakcanary.internal.HeapAnalyzerService" android:enabled="false" android:process=":leakcanary" />
分析hprof文件,設計IO操做,放在leakcanary獨立進程固然更好啦,此時沒有不是單獨開線程的方式
HeapAnalyzer.java
public final class HeapAnalyzer { private final ExcludedRefs excludedRefs; public HeapAnalyzer(ExcludedRefs excludedRefs) { this.excludedRefs = excludedRefs; } public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) { .......... try { //開始解析hropf,HAHA庫中的類HprofBuffer,HprofParser,Snapshot HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); HprofParser parser = new HprofParser(buffer); Snapshot snapshot = parser.parse(); Instance leakingRef = findLeakingReference(referenceKey, snapshot); //findLeakingReference檢查該對象是否 // False alarm, weak reference was cleared in between key check and heap dump. if (leakingRef == null) { //爲null說明已經被GC回收啦,那麼直接return noLeak類型,DisplayLeakService就不顯示啦 return noLeak(since(analysisStartNanoTime)); } return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); //走到這裏,就是計算到GC roots的最短強引用路徑,並肯定是不是泄露。若是是的話,創建致使泄露的引用鏈 } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } } private Instance findLeakingReference(String key, Snapshot snapshot) { ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName()); //HAHA類庫snapshot的findclass方法 List<String> keysFound = new ArrayList<>(); for (Instance instance : refClass.getInstancesList()) { List<ClassInstance.FieldValue> values = classInstanceValues(instance); String keyCandidate = asString(fieldValue(values, "key")); if (keyCandidate.equals(key)) { return fieldValue(values, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound); } //計算到GC roots的最短強引用路徑,並肯定是不是泄露。若是是的話,創建致使泄露的引用鏈 private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef) { ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs); //到GCroot最短路勁ShortestPathFinder,此時排除了SDK自帶問題的excludedRefs ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); // False alarm, no strong reference path to GC Roots. if (result.leakingNode == null) { return noLeak(since(analysisStartNanoTime)); } LeakTrace leakTrace = buildLeakTrace(result.leakingNode); //buildLeakTrace方法創建致使泄露的引用鏈 String className = leakingRef.getClassObj().getClassName(); // Side effect: computes retained size. snapshot.computeDominators(); Instance leakingInstance = result.leakingNode.instance; long retainedSize = leakingInstance.getTotalRetainedSize(); retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance); return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime)); } private LeakTrace buildLeakTrace(LeakNode leakingNode) //創建致使泄露的引用鏈 private LeakTraceElement buildLeakElement(LeakNode node) //創建致使泄露的引用鏈元素
最後來看下app進程中的DisplayLeakService用來顯示內存泄漏的引用鏈的IntentService
public class DisplayLeakService extends AbstractAnalysisResultService { @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) { String leakInfo = leakInfo(this, heapDump, result, true); CanaryLog.d(leakInfo); boolean resultSaved = false; boolean shouldSaveResult = result.leakFound || result.failure != null; //leakFound表示木有內存泄漏 if (shouldSaveResult) { heapDump = renameHeapdump(heapDump); resultSaved = saveResult(heapDump, result); } PendingIntent pendingIntent; String contentTitle; String contentText; if (!shouldSaveResult) { contentTitle = getString(R.string.leak_canary_no_leak_title); contentText = getString(R.string.leak_canary_no_leak_text); pendingIntent = null; } else if (resultSaved) { pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey); if (result.failure == null) { String size = formatShortFileSize(this, result.retainedHeapSize); String className = classSimpleName(result.className); if (result.excludedLeak) { //注意這裏的excludedLeak contentTitle = getString(R.string.leak_canary_leak_excluded, className, size); } else { contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size); } } else { contentTitle = getString(R.string.leak_canary_analysis_failed); } contentText = getString(R.string.leak_canary_notification_message); } else { contentTitle = getString(R.string.leak_canary_could_not_save_title); contentText = getString(R.string.leak_canary_could_not_save_text); pendingIntent = null; } showNotification(this, contentTitle, contentText, pendingIntent); afterDefaultHandling(heapDump, result, leakInfo); } ..... }
第一行的leakInfo打印結果:
leakInfo:In com.example.leakcanary:1.0:1. * com.example.leakcanary.MainActivity has leaked: * GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1') * references com.example.leakcanary.MainActivity$2.this$0 (anonymous subclass of android.os.AsyncTask) * leaks com.example.leakcanary.MainActivity instance
至此分析完畢
Debug.dumpHprofData方法生成hprof不能直接在MAT中查看,須要使用android提供的hprof-conv轉換
hprof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof爲原始文件,yyyyy.hprof爲轉換事後的文件
參考:根搜索算法
在主流的商用程序語言中(Java和C#,甚至包括前面提到的古老的Lisp),都是使用根搜索算法(GC Roots Tracing)斷定對象是否存活的。這個算法的基本思路就是經過一系列的名爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來講就是從GC Roots到這個對象不可達)時,則證實此對象是不可用的。如圖3-1所示,對象object 五、object 六、object 7雖然互相有關聯,可是它們到GC Roots是不可達的,因此它們將會被斷定爲是可回收的對象。
在Java語言裏,可做爲GC Roots的對象包括下面幾種:
1. 虛擬機棧(棧幀中的本地變量表)中的引用的對象。
2. 方法區中的類靜態屬性引用的對象。
3. 方法區中的常量引用的對象。
4. 本地方法棧中JNI(即通常說的Native方法)的引用的對象。
不管是經過引用計數算法判斷對象的引用數量,仍是經過根搜索算法判斷對象的引用鏈是否可達,斷定對象是否存活都與「引用」有關。在JDK 1.2以前,Java中的引用的定義很傳統:若是reference類型的數據中存儲的數值表明的是另一塊內存的起始地址,就稱這塊內存表明着一個引用。這種定義很純粹,可是太過狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態,對於如何描述一些「食之無味,棄之惋惜」的對象就顯得無能爲力。咱們但願能描述這樣一類對象:當內存空間還足夠時,則能保留在內存之中;若是內存在進行垃圾收集後仍是很是緊張,則能夠拋棄這些對象。不少系統的緩存功能都符合這樣的應用場景。
在JDK 1.2以後,Java對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)四種,這四種引用強度依次逐漸減弱。
1. 強引用就是指在程序代碼之中廣泛存在的,相似「Object obj = new Object()」這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
2. 軟引用用來描述一些還有用,但並不是必需的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中並進行第二次回收。若是此次回收仍是沒有足夠的內存,纔會拋出內存溢出異常。在JDK 1.2以後,提供了SoftReference類來實現軟引用。
3. 弱引用也是用來描述非必需對象的,可是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。當垃圾收集器工做時,不管當前內存是否足夠,都會回收掉只被弱引用關聯的對象。在JDK 1.2以後,提供了WeakReference類來實現弱引用。
4. 虛引用也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是但願能在這個對象被收集器回收時收到一個系統通知。在JDK 1.2以後,提供了PhantomReference類來實現虛引用。
引用計數法是惟一沒有使用根集的垃圾回收的法,該算法使用引用計數器來區分存活對象和再也不使用的對象。通常來講,堆中的每一個對象對應一個引用計數器。當每一次建立一個對象並賦給一個變量時,引用計數器置爲1。當對象被賦給任意變量時,引用計數器每次加1當對象出了做用域後(該對象丟棄再也不使用),引用計數器減1,一旦引用計數器爲0,對象就知足了垃圾收集的條件。 基於引用計數器的垃圾收集器運行較快,不會長時間中斷程序執行,適宜地必須 實時運行的程序。但引用計數器增長了程序執行的開銷,由於每次對象賦給新的變量,計數器加1,而每次現有對象出了做用域生,計數器減1。 ps:用根集的方法(既有向圖的方法)進行內存對象管理,能夠消除循環引用的問題.就是說若是有三個對象相互引用,只要他們和根集是不可達的,gc也是能夠回收他們.根集的方法精度很高,可是效率低.計數器法精度低(沒法處理循環引用),可是執行效率高.