本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈java
java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueueandroid
一步步拆解 LeakCanarygithub
內存泄露,一直是咱們性能優化方面的重點。今天,就讓咱們一塊兒來拆解 LeakCanary,一步步理解它的原理性能優化
講解 LeakCannary 原理以前,咱們先來講一下它的主要原理,給你們吃顆定心丸,其實挺簡單的,大概能夠分爲如下幾步:bash
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
複製代碼
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
public final class ServiceHeapDumpListener implements HeapDump.Listener {
private final Context context;
private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
public ServiceHeapDumpListener(Context context,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
// 啓動後臺服務監聽
setEnabled(context, listenerServiceClass, true);
// 啓動 HeapAnalyzerService ,用來分析 dump 文件
setEnabled(context, HeapAnalyzerService.class, true);
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
----
}
複製代碼
listenerServiceClass() 方法綁定了一個後臺服務 DisplayLeakService,這個服務主要用來分析內存泄漏結果併發送通知。你能夠繼承並重寫這個類來進行一些自定義操做,好比上傳分析結果等。微信
public final T excludedRefs(ExcludedRefs excludedRefs) {
this.excludedRefs = excludedRefs;
return self();
}
AndroidExcludedRefs.java
/**
* This returns the references in the leak path that can be ignored for app developers. This
* doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs * in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app * developers except by resorting to serious hacks, so we remove the noise caused by those leaks. */ public static ExcludedRefs.Builder createAppDefaults() { return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class)); } public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) { ExcludedRefs.Builder excluded = ExcludedRefs.builder(); for (AndroidExcludedRefs ref : refs) { if (ref.applies) { ref.add(excluded); ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name()); } } return excluded; } 複製代碼
excludedRefs() 方法定義了一些對於開發者能夠忽略的路徑,意思就是即便這裏發生了內存泄漏,LeakCanary 也不會彈出通知。這大可能是系統 Bug 致使的,無需用戶進行處理。併發
buildAndInstall 所作的工做,調用 build 構建 refWatcher,判斷 refWatcher 是否 DISABLED,若不是 DISABLED 狀態,調用 install 方法,並將 refWatcher 返回回去app
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
// 構建 refWatcher 對象
RefWatcher refWatcher = build();
// 判斷是否 DISABLED,若不是 DISABLED 狀態,調用
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
複製代碼
瞭解 build 方法 以前,咱們先來看一下 RefWatcherBuilder 是什麼東東?
public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
private ExcludedRefs excludedRefs;
private HeapDump.Listener heapDumpListener;
private DebuggerControl debuggerControl;
private HeapDumper heapDumper;
private WatchExecutor watchExecutor;
private GcTrigger gcTrigger;
/** Creates a {@link RefWatcher}. */
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}
----
複製代碼
build 方法看到這裏你是否是有一種很眼熟的感受,沒錯,它運用了建造者模式,與咱們 Android 中的 AlertDialog.build 同出一轍。 建造者模式(Builder)及其應用
RefWatcherBuilder 主要有幾個重要的成員變量
ActivityRefWatcher.install((Application) context, refWatcher);
複製代碼
public final class ActivityRefWatcher {
/** @deprecated Use {@link #install(Application, RefWatcher)}. */
@Deprecated
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
install(application, refWatcher);
}
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
private final Application application;
private final RefWatcher refWatcher;
/**
* Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
* after they have been destroyed.
*/
public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
this.application = checkNotNull(application, "application");
this.refWatcher = checkNotNull(refWatcher, "refWatcher");
}
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
public void watchActivities() {
// Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } public void stopWatchingActivities() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); } } 複製代碼
install 來講,主要作如下事情
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
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();
// 添加到 set 集合中
retainedKeys.add(key);
// 穿件 KeyedWeakReference 對象
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
複製代碼
咱們先來看一下 KeyedWeakReference ,能夠看到 KeyedWeakReference 繼承於 WeakReference,並定義了 key,name 字段
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");
}
}
複製代碼
弱引用和引用隊列 ReferenceQueue 聯合使用時,若是弱引用持有的對象被垃圾回收,Java 虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。即 KeyedWeakReference 持有的 Activity 對象若是被垃圾回收,該對象就會加入到引用隊列 queue 中。具體的能夠參考個人這一篇博客 java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueue
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
複製代碼
ensureGoneAsync 這個方法,在 watchExecutor 的回調裏面執行了 ensureGone 方法,watchExecutor 是 AndroidWatchExecutor 的實例。
接下來,咱們一塊兒來看一下 watchExecutor,主要關注 execute 方法
public final class AndroidWatchExecutor implements WatchExecutor {
static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
private final Handler mainHandler;
private final Handler backgroundHandler;
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(Retryable retryable) {
// 當前線程是主線程
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else { // 當前線程不是主線程
postWaitForIdle(retryable, 0);
}
}
--------
}
複製代碼
execute 方法,首先判斷是不是主線程,若是是主線程,調用 waitForIdle 方法,等待空閒的時候執行,若是不是主線程,調用 postWaitForIdle 方法。咱們一塊兒來看一下 postWaitForIdle 和 waitForIdle 方法。
// 調用 mainHandler 的 post 方法,,確保在主線程中執行
void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
// 噹噹前線程 looper 空閒的時候執行
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
// 當 looper 空閒的時候,會回調 queueIdle 方法
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
複製代碼
能夠看到 postWaitForIdle 方法實際上是 調用 mainHandler 的 post 方法,,確保在主線程中執行,以後再 runnable 的 run 方法在調用 waitForIdle 方法。而 waitForIdle 方法是在等當前 looper 空閒以後,執行 postToBackgroundWithDelay 方法
void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
// 取 Math.pow(2, failedAttempts), maxBackoffFactor 的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,
// 第一次執行的時候 failedAttempts 是 0 ,因此 exponentialBackoffFactor 是1
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
// initialDelayMillis 的默認值是 5
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
// 因此第一次延遲執行的時候是 5s,若
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
// 過 result == RETRY,再次調用 postWaitForIdle,下一次的 delayMillis= 上一次的 delayMillis *2;
// 正常狀況下,不會返回 RETRY,當 heapDumpFile == RETRY_LATER (即 dump heap 失敗的時候),會返回 RETRY
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
複製代碼
postToBackgroundWithDelay 方法有點相似遞歸,正常狀況下,若 retryable.run() 返回的結果不等於 RETRY,只會執行一次。若 retryable.run() 返回 RETRY,則會執行屢次,退出的條件是 retryable.run() 返回結果不等於 RETRY;
delay 的時間 取 Math.pow(2, failedAttempts), maxBackoffFactor 兩個數的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,而,第一次執行的時候 failedAttempts 是 0 ,因此 exponentialBackoffFactor 是 1,即 delayMillis = initialDelayMillis * exponentialBackoffFactor= 5*1=5;
所以,綜合上面的例子,第一次執行的時間是 activity destroy 以後 5s。
OK,咱們回到 ensureGone 方法,這纔是咱們的重點
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 移除已經被回收的引用
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 判斷 reference,即 activity 是否內回收了,若被回收了,直接返回
if (gone(reference)) {
return DONE;
}
// 調用 gc 方法進行垃圾回收
gcTrigger.runGc();
// 移除已經被回收的引用
removeWeaklyReachableReferences();
// activity 尚未被回收,證實發生內存泄露
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// dump heap,並生成相應的 hprof 文件
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {// dump the heap 失敗的時候
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 分析 hprof 文件
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
複製代碼
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;
// 遍歷 queue ,並從 retainedKeys set 集合中移除
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
複製代碼
gone(reference) 方法,判斷 retainedKeys set 集合,是否還含有 reference,若沒有,證實已經被回收了;若含有,可能已經發生內存泄露。由於咱們知道 refrence 被回收的時候,會被加進 queue 裏面,值調用 gone 方法判斷的時候,咱們已經遍歷 queue 移除掉 retainedKeys 裏面的 refrence,若含有,證實 refrence 沒有被回收,之因此說可能發生內存泄露,是由於 gc 回收器可能尚未回收。
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
複製代碼
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move // references to the appropriate queues. try { Thread.sleep(100); } catch (InterruptedException e) { throw new AssertionError(); } } }; 複製代碼
ok,咱們在回到 ensureGoneAsync 方法,整理一下它的流程
這裏主要是調用 AndroidHeapDumper 的 dumpHeap 方法,而裏面比較重要的是調用 Debug.dumpHprofData 生成 hprof 文件。
AndroidHeapDumper#dumpHeap
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
@Override public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
複製代碼
當發生了泄漏就會生成 HeapDump 對象而後就會進入下面這個方法去啓動 HeapAnalyzerServiceService 來進行分析
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
複製代碼
關於如解析 hprof,請自行了解 haha 庫的用法即原理
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
複製代碼
通過解析以後機會把數據傳遞到 DisplayLeakService ,Service 會根據傳入進來的數據發送通知欄通知,當你點擊對應的通知進入DisplayLeakActivity界面就能顯示泄漏日誌了。
LeakCanary 的原理總結以下
其中,比較重要的是如何肯定是否發生內存泄露,而如何肯定發生內存泄露最主要的原理是經過 Refrence 和 RefrenceQueue。悄悄地提醒你一下,面試必備。
最後,用一張圖片來表示 leakCannary 的執行流程,該圖片來自 深刻理解 Android 之 LeakCanary 源碼解析
java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueue
賣一下廣告,歡迎你們關注個人微信公衆號,掃一掃下方二維碼或搜索微信號 stormjun,便可關注。 目前專一於 Android 開發,主要分享 Android開發相關知識和一些相關的優秀文章,包括我的總結,職場經驗等。