本文已在公衆號郭霖原創發佈。版權歸微信公衆號郭霖全部,未經許可,不得以任何形式轉載,微信文章地址:java
1.Activity onDestroy以後將它放在一個WeakReference。git
2.這個WeakReference關聯到一個ReferenceQueue。github
3.查看ReferenceQueue是否存在Activity的引用。算法
4.若是該Activity泄露了,Dump出heap信息,而後再去分析泄露路徑。微信
軟引用&弱引用併發
軟引用(SoftReference)和弱引用(WeakReference)都繼承Reference。app
軟引用:當一個對象只有軟引用存在時,系統內存不足時會被gc回收。dom
弱引用:當一個對象只有弱引用存在時,隨時被gc回收。異步
對象被回收後,Java虛擬機就會把這個引用加入到與之關聯的引用隊列中。
//java.lang.ref.Reference.java
public abstract class Reference<T> {
...
volatile T referent;
final ReferenceQueue<? super T> queue;
...
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = queue;
}
...
/** * <p> This method is invoked only by Java code; when the garbage collector * enqueues references it does so directly, without invoking this method. *(這個方法僅會被java代碼調用,當GC時會直接把referent添加到queue引用隊列) * */
public boolean enqueue() {
return queue != null && queue.enqueue(this);
}
}
複製代碼
簡單實例:
public class RefTest {
public static void main(String[] args) {
//user爲強引用
User user = new User("張三", 18);
//建立弱引用並關聯引用隊列
ReferenceQueue<User> queue = new ReferenceQueue<>();
WeakReference<User> weakReference = new WeakReference<User>(user,queue);
//置空強引用,觸發GC
user=null;
Runtime.getRuntime().gc();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//強制系統回收已經沒有強引用的對象
System.runFinalization();
WeakReference pollRef=null;
//彈出對列的引弱用
while ((pollRef = (WeakReference) queue.poll()) != null) {
System.out.println("pollRef的內存地址:"+pollRef.toString());
System.out.println("pollRef等於weakReference?:"+weakReference.equals(pollRef));
}
}
}
複製代碼
運行結果(注意內存地址是由JVM分配的,故可能有所差別):
pollRef的內存地址:java.lang.ref.WeakReference@610455d6 pollRef等於weakReference?:true
Java垃圾回收(GC) 在Java中垃圾判斷方法是 可達性分析算法,這個算法的基本思路是經過一系列的"GC Root"的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑成爲引用鏈,當一個對象到GC Root沒有任何引用鏈相連時,則證實此對象是不可用的。 GC Root的對象包括如下幾種:
一、虛擬機棧中引用的對象。
二、方法區中類靜態屬性引用的對象。
三、方法區中常量引用的對象。
四、本地方法棧中JNI引用的對象。
就算一個對象,經過可達性分析算法分析後,發現其是『不可達』的,也並非非回收不可的。 通常狀況下,要宣告一個對象死亡,至少要通過兩次標記過程:
一、通過可達性分析後,一個對象並無與GC Root關聯的引用鏈,將會被第一次標記和篩選。篩選條件是此對象有沒有必要執行finalize()方法。若是對象沒有覆蓋finalize()方法,或者已經執行過了。那就認爲他能夠回收了。若是有必要執行finalize()方法,那麼將會把這個對象放置到F-Queue的隊列中,等待執行。
二、虛擬機會創建一個低優先級的Finalizer線程執行F-Queue裏面的對象的finalize()方法。若是對象在finalize()方法中能夠『拯救』本身,那麼將不會被回收,不然,他將被移入一個即將被回收的ReferenceQueue。
首先在gradle引入依賴
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
LeakCanary在Application初始化,代碼以下:
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//檢查當前進程是否在HeapAnalyzerService所屬進程
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
//安裝泄露檢測
LeakCanary.install(this);
}
}
複製代碼
首先調用isInAnalyzerProcess()來判斷當前進程是否爲HeapAnalyzerService運行的進程。這個方法回調用LeakCanaryInternals.isInServiceProcess()經過PackageManager、ActivityManager以及android.os.Process來判斷當前進程是否爲HeapAnalyzerService運行的進程,這樣子作的目的是不影響主進程的使用。下面是debug生成的AndroidManifest.xml,能夠在run應用以後再app/build/intermediates/instant_app_manifest/debug/查看。
<!-- 這個是LeakCanary分析泄露的Service -->
<service android:name="com.squareup.leakcanary.internal.HeapAnalyzerService" android:enabled="false" android:process=":leakcanary" />
<!-- 這個是LeakCanary展現泄露的Service -->
<service android:name="com.squareup.leakcanary.DisplayLeakService" android:enabled="false" android:process=":leakcanary" />
<!-- 這個是LeakCanary顯示泄露信息的Activity,由於被設置爲Launcher,並設置了金絲雀的icon,因此使用LeakCanar纔會在桌面上生成icon入口的緣由。 -->
<activity android:name="com.squareup.leakcanary.internal.DisplayLeakActivity" android:enabled="false" android:icon="@mipmap/leak_canary_icon" android:label="@string/leak_canary_display_activity_label" android:process=":leakcanary" android:taskAffinity="com.squareup.leakcanary.com.pengguanming.studydemo" android:theme="@style/leak_canary_LeakCanary.Base" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
複製代碼
注意上面關於LeakCanary的組件的android:enabled=false,android:enabled表示是否可以實例化該應用程序的組件,若是爲true,每一個組件的enabled屬性決定那個組件是否能夠被 enabled。若是爲false,它覆蓋組件指定的值;全部組件都是disabled。
這裏回過來看install()方法,它調用返回RefWatcher對象,這個對象經過Application註冊了Activity的生命週期監聽、經過Activity註冊監聽Fragment的生命週期,且用到了leakcanary-support-fragment包,兼容了v4的fragment。
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())//默認過濾一些系統bug形成的內存泄露
.buildAndInstall();
}
複製代碼
值得注意的是LeakCanary.refWatcher(application)返回的是一個AndroidRefWatcherBuilder對象,下面看看它的buildAndInstall():
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
//判斷是否開啓內存泄露提示
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
//判斷是否開啓Activity內存泄露檢測
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
//判斷是否開啓Fragment內存泄露檢測
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
//複製給全局靜態變量,防止二次調用
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
複製代碼
首先判斷refWatcher是否已被初始,build()建立RefWatcher對象,而後判斷這對象是否是RefWatch是否可用,若可用則執行下面操做:
1.檢測DisplayLeakActivity是否可用,若不可用則調用LeakCanaryInternals.setEnabledAsync()調用AsyncTask.THREAD_POOL_EXECUTOR這個靜態異步線程池執行PackageManager.setComponentEnabledSetting()將這個Activity設置爲可用。PackageManager.setComponentEnabledSetting()是IPC的阻塞操做,故做異步處理。
2.判斷是否開啓Activity內存泄露檢測,若沒則調用ActivityRefWatcher.install()會建立ActivityRefWatcher對象而後經過Application註冊Activity的生命週期監聽。
3.判斷是否開啓Fragment內存泄露檢測,若沒則調用FragmentRefWatcher.Helper.install(),經過Activity註冊監聽Fragment的生命週期,且用到了leakcanary-support-fragment包,兼容了v4的fragment。
上面提到Activity和Fragment的生命週期監聽,這裏首先看看監聽Activity的代碼:
//ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
複製代碼
上面經過Application註冊了ActivityRefWatcher成員lifecycleCallbacks監聽Activity生命週期回調,lifecycleCallbacks是繼承Application.ActivityLifecycleCallbacks的抽想類,這樣就完成了Activity銷燬時監聽監聽回調,並執行Activity內存泄露檢測操做。下面看看它的代碼實現:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
複製代碼
如今再來看看監聽Fragment的代碼:
//FragmentRefWatcher.Helper.install()
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
if (SDK_INT >= O) {
//添加兼容android O的Fragment泄露檢測
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
//添加經過反射構造兼容android O如下的Fragment泄露檢測
try {
Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
Constructor<?> constructor =
fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
FragmentRefWatcher supportFragmentRefWatcher =
(FragmentRefWatcher) constructor.newInstance(refWatcher);
fragmentRefWatchers.add(supportFragmentRefWatcher);
} catch (Exception ignored) {
}
if (fragmentRefWatchers.size() == 0) {
return;
}
Helper helper = new Helper(fragmentRefWatchers);
//註冊監聽Activity生命週期回調
Application application = (Application) context.getApplicationContext();
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
複製代碼
上面代碼實現和監聽Activity生命週期有所差別,首先建立FragmentRefWatcher的容器,判斷SDK版本是否大於等於Android O,若大於等於則建立AndroidOFragmentRefWatche加入容器,而後在經過反射建立SupportFragmentRefWatcher也加入到容器中,以後建立Helper並經過Application註冊了Helper成員activityLifecycleCallbacks監聽Activity的生命週期,但它僅監聽Activity的建立,下面來看看它的代碼:
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
複製代碼
經過上面的代碼能夠知道,這個activityLifecycleCallbacks在Activity建立時,遍歷以前 FragmentRefWatcher的list並調用實例中的watchFragments(),list只有兩個對象:SupportFragmentRefWatcher(兼容android O如下)和AndroidOFragmentRefWatcher(兼容android O+引入了fragment的生命週期,用戶不須要在onDestroy中自行調用),它們兩實現差很少,這裏只看SupportFragmentRefWatcher代碼:
class SupportFragmentRefWatcher implements FragmentRefWatcher {
...
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
//在Fragment的View銷燬時檢測泄露
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
//在Fragment實例銷燬時檢測泄露
refWatcher.watch(fragment);
}
};
@Override public void watchFragments(Activity activity) {
if (activity instanceof FragmentActivity) {
//註冊supportFragment生命週期監聽
FragmentManager supportFragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
}
複製代碼
到watchFragments()這裏發現這才和監聽Activity生命週期類似,經過Activity獲取FragmentManager註冊成員FragmentLifecycleCallbacks監聽Fragment銷燬生命週期,而後進行內存泄露檢測操做。
在【2.2】小節裏得知在Activity和Fragment銷燬時都會拿調用RefWatch.watch()方法,在此以前先了解一下RefWatch對象:
/
*@param WatchExecutor 用於執行檢測內存的線程控制器
*@param DebuggerControl 查詢是否正在debug中,若正在debug會不執行內存泄露的檢測判斷
*@param GcTrigger 用來觸發垃圾回收的,上面的線程控制器5s後觀察有泄露,不算泄露,必須垃圾回收後,再去觀察一次。因此最多會觀察兩次。第一次是5s後觀察,第二次是5s後在垃圾回收後觀察
*@param HeapDump dump出內存泄露的heap堆文件
*@param HeapDump.Listener 產生heap文件的回調
*@param ExcludeRefs 過濾掉的內存泄露
/
RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) {
this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
this.heapDumper = checkNotNull(heapDumper, "heapDumper");
this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener");
this.excludedRefs = checkNotNull(excludedRefs, "excludedRefs");
//持有待檢測內存泄露引用的key,這裏使用CopyOnWriteArraySet解決併發讀寫問題
retainedKeys = new CopyOnWriteArraySet<>();
//引用隊列,弱引用或軟引用被gc回收後會達到此隊列
queue = new ReferenceQueue<>();
}
複製代碼
上面代碼讓咱們有個大概認識,這小節只關注retainedKeys和queue成員變量就夠了,其餘成員變量會在以後分析到,咱們先看看RefWatch.watch():
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
//檢查泄露開始時間
final long watchStartNanoTime = System.nanoTime();
//爲引用生成惟一的key
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
//建立一個弱引用
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//開啓異步線程分析弱引用
ensureGoneAsync(watchStartNanoTime, reference);
}
複製代碼
首先會判斷當前RefWatch是否可用,而後對檢測對象和對應的引用標識字符串判空,生成檢查泄露開始時間,接下來爲引用生成惟一的key並添加到retainedKeys,而後建立KeyedWeakReference對象,開啓異步線程分析弱引用。這裏看看KeyedWeakReference代碼:
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");
}
}
複製代碼
從上面能夠看出KeyedWeakReference封裝了引用惟一的key和引用標識字符串,並將檢測對象的弱引用關聯到RefWatch的引用隊列。
從上一小節看到RefWatch.ensureGoneAsync(),下面看看代碼實現:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
複製代碼
有上面代碼看出將泄露檢測轉移到WatchExecutor,而它的實現是AndroidWatchExecutor,接下來看看它的代碼:
public final class AndroidWatchExecutor implements WatchExecutor {
static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";//子線程名稱
private final Handler mainHandler;//綁定主線程的Handler
private final Handler backgroundHandler;//綁定子線程的Handler
private final long initialDelayMillis;//初始化延遲的毫秒數
private final long maxBackoffFactor;//最大失敗重試數
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
@Override public void execute(@NonNull Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
//主線程操做
waitForIdle(retryable, 0);
} else {
//子線程切換到主線程操做
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
//調用mainHandler將操做切換到主線程
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.(這裏必定要在主線程調用)
//添加主線程空閒回調監聽執行postToBackgroundWithDelay()
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
//指數2增加的重試數,最大值不超過maxBackoffFactor
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
//延遲執行的時間
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
//切換到後臺子線程執行
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
//若是返回值爲RETRY時,會再次延時再次嘗試執行。延遲初始時間爲5s,之後每次重試時間x2
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
複製代碼
1.AndroidWatchExecutor的構造方法會建立主線程和和後臺子線的Handler,並初始化切換到子線程Handler的最大延遲時間maxBackoffFactor和初始化時間initialDelayMillis,maxBackoffFactor值爲Long.MAX_VALUE / initialDelayMillis,initialDelayMillis值爲5(由於AndroidWatchExecutor是被AndroidRefWatcherBuilder.defaultWatchExecutor()建立)。
2.execute()裏的不管waitForIdle()仍是postWaitForIdle(),都是須要切換到主線程執行,並且postWaitForIdle()最終切換到waitForIdle()。
3.waitForIdle()監聽主線程Handler消息隊列空閒,只要主線程空閒就會執行postToBackgroundWithDelay()操做。
4.postToBackgroundWithDelay()會就首先根據重試次數計算延遲執行的時間,而後延遲切換到子線程的Handler操做,若是Retryable.run()返回Result.RETRY時,會執行postWaitForIdle()繼續等待主線程再次空閒。
5.Retryable.run()在以前RefWatcher.ensureGoneAsync()被調用,而Retryable.run()的返回值由RefWatcher.ensureGoneAsync()返回,ensureGoneAsync()在如下的狀況會返回Result.RETRY:
A.debug模式啓動時。
B.建立dumpHeap文件失敗時(見【2.5.6泄漏判斷】)。
C.5s後UI線程未空閒時(見【2.5.6泄漏判斷】)。
RefWatch.ensureGone()主要是判斷內存泄露,下面看看它的實現:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
//計算watch方法到gc垃圾回收的時長
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//嘗試移除已經到達引用隊列的弱引用
removeWeaklyReachableReferences();
//判斷是否在debug
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks. (debug能夠創造錯誤的內存泄露)
return RETRY;
}
if (gone(reference)) {//若當前對象已經可達了,即不會形成你內存泄露
return DONE;
}
//手動gc,確保引用對象是否真的被回收了。由於在dump內存信息以前提示內存泄露的時候,但願系統通過充分gc垃圾回收,而不存在任何的誤判,對leakcanary容錯性的考慮
gcTrigger.runGc();
//清除已經到達引用隊列的弱引用
removeWeaklyReachableReferences();
if (!gone(reference)) {//此時對象還沒到達對列,表明已經內存泄露了
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//dump出內存泄露的heap文件,這裏可能觸發GC
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.(不能dump heap堆文件)
return RETRY;
}
//dump heap文件的時間計算
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//真正分析內存泄露以及路徑
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
複製代碼
上面代碼執行以下:
1.在以前的RefWatch.watch()裏生成了檢測引用對象的UUID的key並關聯了弱引用KeyedWeakReference對象,弱引用與ReferenceQueue聯合使用,若是弱引用關聯的對象被回收,則會把這個弱引用加入到ReferenceQueue中。
2.removeWeaklyReachableReferences()嘗試移除已經到達引用隊列的弱引用,會對應刪除KeyedWeakReference的數據。若是這個引用繼續存在,那麼就說明沒有被回收。
3.gone()查看retainedKeys是否包含KeyedWeakReference的惟一key。
4.手動觸發GC操做,gcTrigger中封裝了gc操做的代碼,首先會調用Runtime.getRuntime().gc()以觸發系統gc操做,而後當先後臺子線程sleep 100毫秒,最後調用System.runFinalization()強制系統回收沒有引用的隊形,這樣子確保引用對象是否真的被回收了。由於在dump內存信息以前提示內存泄露的時候,但願系統通過充分gc垃圾回收,而不存在任何的誤判,這是對leakcanary容錯性的考慮。
5.再次移除不可達引用,若是引用存在了,都沒有被回收則斷定內存泄露。
6.斷定泄露後調用AndroidHeapDumper.dump(),首先經過LeakDirectoryProvider的實現類DefaultLeakDirectoryProvider爲.prof文件建立File,若文件建立失敗也會返回RETRY_LATER讓以前的AndroidWatchExecutor.execute()等待下次主線程空閒執行,它最多建立7個文件,數目超事後,刪除最先建立的文件,全部文件默認保存在Download文件夾下;而後利用CountDownLatch阻塞當先後臺子線程5秒並監聽主線程是否空閒,若不空閒則返回RETRY_LATER,若空閒則調用android.os.Debug.dumpHprofData()生成.prof文件。
7.調用HeapDump.Listener分析剛生成的.prof文件。
既然判斷了內存泄露,那麼接下來泄露信息分析,找出泄露的對象的引用路徑。 ServiceHeapDumpListener是HeapDump.Listener的實現類,在RefWatch.ensureGone()中調用了它的analyze():
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
複製代碼
HeapAnalyzerService繼承抽象類ForegroundService,而ForegroundService繼承IntentService,它的runAnalysis()會回調onHandleIntent():
public final class HeapAnalyzerService extends ForegroundService implements AnalyzerProgressListener {
...
public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
//開啓Service實例化
setEnabledBlocking(context, HeapAnalyzerService.class, true);
setEnabledBlocking(context, listenerServiceClass, true);
//啓動前臺服務
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
ContextCompat.startForegroundService(context, intent);
}
...
@Override
protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
//建立過濾掉特定內存泄露的heapAnalyzer
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//檢查內存泄露
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
//回調結果顯示
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
}
複製代碼
HeapAnalyzer的checkForLeak():是leakcannary最核心的方法
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,@NonNull String referenceKey,boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
//封裝成MemoryMappedFileBuffer對象
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
//傳進HprofParser解析器
HprofParser parser = new HprofParser(buffer);
//裝換成Snapshot內存快照對象
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
//去重複路徑結果
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
deduplicateGcRoots(snapshot);
//根據要檢測的key查詢解析結果中是否有須要的對象
listener.onProgressUpdate(FINDING_LEAKING_REF);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.(誤報,弱引用在對應的KeyedWeakReference檢查和heap堆文件期間被清除)
if (leakingRef == null) {//引用對象不存在,說明gc時被清除
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
//找出內存泄露的路徑
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
複製代碼
1.引入HAHA庫(一個heap prof堆文件分析庫),將hprof文件解析成內存快照Snapshot對象進行分析。
2.deduplicateGcRoots()使用jetBrains的THashMap(THashMap的內存佔用量比HashMap小)作中轉,去掉snapshot中GCRoot的重複路徑,以減小內存壓力。
3.找出泄露對象並找出泄露對象的最短路勁。
HeapAnalyzer.findLeakingReference()主要做用是找出泄露對象:
private Instance findLeakingReference(String key, Snapshot snapshot) {
//經過查找的弱引用建立ClassOb對象
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
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)) {//key值相等則找到內存泄露對象
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
複製代碼
1.在snpashot內存快照中找到泄露對象的弱引用。
2.遍歷這個對象全部實例。
3.若這個key值和最開始定義封裝KeyedWeakReference的key值相同,那麼返回這個泄露對象。
HeapAnalyer.findLeakTract()主要做用是找到最短泄露路徑,計算泄露大小做爲結果反饋:
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef, boolean computeRetainedSize) {
//分析snpashot內存快照找出內存泄露的點,判斷依據是GCRoot
listener.onProgressUpdate(FINDING_SHORTEST_PATH);
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
//查找泄露的最短引用鏈,這裏只關注GCRoot的兩種類型:1.靜態;2.被其餘線程使用而且其餘線程正在運行沒有結束
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
String className = leakingRef.getClassObj().getClassName();
// False alarm, no strong reference path to GC Roots.(誤報,沒有強引用的GCRoots)
if (result.leakingNode == null) {
return noLeak(className, since(analysisStartNanoTime));
}
listener.onProgressUpdate(BUILDING_LEAK_TRACE);
//生成內存泄露調用棧
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
//計算內存泄露空間大小
long retainedSize;
if (computeRetainedSize) {
listener.onProgressUpdate(COMPUTING_DOMINATORS);
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
} else {
retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
複製代碼
在【2.6泄露信息分析】裏HeapAnalyzerService.onHandleIntentInForeground():
...
//HeapAnalyzerService.java
//回調結果顯示
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
複製代碼
AbstractAnalysisResultService.sendResultToListener()經過調用AnalyzedHeap.save()將以前的.prof文件保存爲.result文件,而後將.result文件路徑經過intent傳到DisplayLeakService(繼承AbstractAnalysisResultService,而AbstractAnalysisResultService是繼承ForegroundService),這裏首先調用父類AbstractAnalysisResultService.onHandleIntentInForeground()經過AnalyzedHeap.load()將.result文件生成AnalyzedHeap對象,以後調用onHeapAnalyzed()將AnalyzedHeap的信息經過Notification展現。
DisplayLeakActivity是平時用到的經過桌面入口進入的泄漏信息查看Activity在【見2.1】AndroidRefWatcherBuilder.buildAndInstall()被開啓實例化
//DisplayLeakActivity.java
@Override protected void onResume() {
super.onResume();
LoadLeaks.load(this, getLeakDirectoryProvider(this));
}
複製代碼
在onResume的時候使用了LoadLeaks(實現了Runnable接口),並傳入一個Provider,這個Provider就是上面建立.result文件時所用到的DefaultLeakDirectoryProvider,而在load方法主要在線程池執行是讀取.result文件,而後經過UI Handler將讀取的信息更新ui中。
A.使用DownCountLatch同步主線程和子線程,見【2.5 泄漏判斷】。
B.這裏使用CopyOnWriteArraySet解決併發讀寫問題。
retainedKeys = new CopyOnWriteArraySet<>();
C.構建者模式,代碼簡潔、清新,鏈式調用建立對象,參考RefWatcherBuilder對象。
D.MessageQueue.addIdleHandler(IdleHandler handler),監聽線程空閒。
E.手動GC,參考GCTrigger.runGc()。
F.Reference.watch()本質上是能夠監控任意對象類型的,關鍵在於監控的時機,像activity、service、fragmen是有生命週期的,能夠在ondestroy時開始監控,其餘的對象類型用戶能夠選擇合適的時機調用該方法進行監控。注意若是首頁的Activity一直不銷燬(onDestroy)那麼將一直沒法檢測到首頁的調用棧的內存泄漏。
G.藉助AsyncTask.THREAD_POOL_EXECUTOR靜態線程池執行異步任務。
LeakCanary的源碼設計很是精妙,因爲本人水平有限僅給各位提供參考,但願可以拋磚引玉,若是有什麼能夠討論的問題能夠在評論區留言或聯繫本人。
參考: