Android 內(nèi)存泄露檢測(cè)工具 LeakCanary 的監(jiān)控原理

一. java 的幾種 reference:
1咐刨,強(qiáng)引用(Strong Reference, 沒(méi)有具體的類來(lái)標(biāo)識(shí)強(qiáng)引用码俩,正常的使用的對(duì)象引用都是強(qiáng)引用度帮,由vm實(shí)現(xiàn))
強(qiáng)引用是使用最普遍的引用。如果一個(gè)對(duì)象具有強(qiáng)引用握玛,那垃圾回收器絕不會(huì)回收它够傍。

當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤挠铲,使程序異常終止冕屯,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來(lái)解決內(nèi)存不足的問(wèn)題。

2拂苹,軟引用(SoftReference)

如果一個(gè)對(duì)象只具有軟引用安聘,則內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它瓢棒;如果內(nèi)存空間不足了浴韭,就會(huì)回收這些對(duì)象的內(nèi)存。

只要垃圾回收器沒(méi)有回收它脯宿,該對(duì)象就可以被程序使用念颈。軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存。

軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用连霉,如果軟引用所引用的對(duì)象被垃圾回收器回收榴芳,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中嗡靡。

3,弱引用(WeakReference)

弱引用與軟引用的區(qū)別在于:只具有弱引用的對(duì)象擁有更短暫的生命周期。

在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過(guò)程中窟感,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象讨彼,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存柿祈。

不過(guò)哈误,由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象躏嚎。

弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用蜜自,如果弱引用所引用的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中卢佣。

4袁辈,虛引用(PhantomReference)

“虛引用”顧名思義,就是形同虛設(shè)珠漂,與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期尾膊。如果一個(gè)對(duì)象僅持有虛引用媳危,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收冈敛。

虛引用主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)待笑。虛引用與軟引用和弱引用的一個(gè)區(qū)別在于:虛引用必須和引用隊(duì)列 (ReferenceQueue)聯(lián)合使用。

當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí)抓谴,如果發(fā)現(xiàn)它還有虛引用暮蹂,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之 關(guān)聯(lián)的引用隊(duì)列中癌压。

二.leakcanary原理
LeakCanary 中的 RefWatcher 就是通過(guò)弱引用及其隊(duì)列來(lái)實(shí)現(xiàn)監(jiān)控的:

有兩個(gè)很重要的結(jié)構(gòu): retainedKeys 和 queue 仰泻,

retainedKeys 代表沒(méi)被gc 回收的對(duì)象,

而queue中的弱引用代表的是被gc了的對(duì)象滩届,通過(guò)這兩個(gè)結(jié)構(gòu)就可以監(jiān)控對(duì)象是不是被回收了集侯;

retainedKeys存放了RefWatcher為每個(gè)被監(jiān)控的對(duì)象生成的唯一key;

同時(shí)每個(gè)被監(jiān)控對(duì)象的弱引用(KeyedWeakReference)關(guān)聯(lián)了 其對(duì)應(yīng)的key 和 queue帜消,這樣對(duì)象若被回收棠枉,則其對(duì)應(yīng)的弱引用會(huì)被入隊(duì)到queue中;

removeWeaklyReachableReferences(..)所做的就是把存在與queue中的弱引用的key 從 retainedKeys 中刪除泡挺。

private final Set<String> retainedKeys;
private final ReferenceQueue<Object> queue;

/**

  • Watches the provided references and checks if it can be GCed. This method is non blocking,
  • the check is done on the {@link Executor} this {@link RefWatcher} has been constructed with.
  • @param referenceName An logical identifier for the watched object.
    */
    public void watch(Object watchedReference, String referenceName) {
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    if (debuggerControl.isDebuggerAttached()) {
    return;
    }
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
    new KeyedWeakReference(watchedReference, key, referenceName, queue);
watchExecutor.execute(new Runnable() {
  @Override public void run() {
    ensureGone(reference, watchStartNanoTime);
  }
});

}

void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();

long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (gone(reference) || debuggerControl.isDebuggerAttached()) {
  return;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
  long startDumpHeap = System.nanoTime();
  long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

  File heapDumpFile = heapDumper.dumpHeap();

  if (heapDumpFile == HeapDumper.NO_DUMP) {
    // 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));
}

}

private boolean gone(KeyedWeakReference reference) {
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;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
什么時(shí)候使用RefWatcher進(jìn)行監(jiān)控 辈讶?

對(duì)于android, 若要監(jiān)控Activity娄猫, 需要在其執(zhí)行destroy的 時(shí)候進(jìn)行監(jiān)控:

通過(guò)向Application 注冊(cè) ActivityLifecycleCallback贱除, 在onActivityDestroyed(Activity activity) 中 開(kāi)始監(jiān)聽(tīng) activity對(duì)象生闲, 因?yàn)檫@時(shí)activity應(yīng)該被回收了,若發(fā)生內(nèi)存泄露勘伺,則可以沒(méi)發(fā)現(xiàn)跪腹;

RefWatcher 檢查對(duì)象是否被回收是在一個(gè) Executor 中執(zhí)行的, Android 的監(jiān)控 提供了 AndroidWatchExecutor 飞醉, 它在主線程執(zhí)行冲茸, 但是有一個(gè)delay 時(shí)間(默認(rèn)5000 milisecs), 因?yàn)閷?duì)于application 來(lái)說(shuō)缅帘,執(zhí)行destroy activity只是把必要資源回收轴术,activity 對(duì)象不一定會(huì)馬上被 gc回收。

private void executeDelayedAfterIdleUnsafe(final Runnable runnable) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
backgroundHandler.postDelayed(runnable, DELAY_MILLIS);
return false;
}
});
}
ActivityRefWatcher:

package com.squareup.leakcanary;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
import static com.squareup.leakcanary.Preconditions.checkNotNull;

@TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher {

public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
if (SDK_INT < ICE_CREAM_SANDWICH) {
// If you need to support Android < ICS, override onDestroy() in your base activity.
return;
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
activityRefWatcher.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, final 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);
}
}
若發(fā)生了泄露钦无, refWatcher 會(huì)執(zhí)行dump 逗栽,生成dump 文件,然后由mat 或haha 等分析工具找到泄露對(duì)象的引用路徑失暂。

作者: DocMike
鏈接:https://www.imooc.com/article/19225
來(lái)源:慕課網(wǎng)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末彼宠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弟塞,更是在濱河造成了極大的恐慌凭峡,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件决记,死亡現(xiàn)場(chǎng)離奇詭異摧冀,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)系宫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)索昂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人扩借,你說(shuō)我怎么就攤上這事椒惨。” “怎么了往枷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵框产,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我错洁,道長(zhǎng)秉宿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任屯碴,我火速辦了婚禮描睦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘导而。我一直安慰自己忱叭,他們只是感情好隔崎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著韵丑,像睡著了一般爵卒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撵彻,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天钓株,我揣著相機(jī)與錄音,去河邊找鬼陌僵。 笑死轴合,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碗短。 我是一名探鬼主播受葛,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼偎谁!你這毒婦竟也來(lái)了总滩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤巡雨,失蹤者是張志新(化名)和其女友劉穎咳秉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鸯隅,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年向挖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝌以。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡何之,死狀恐怖跟畅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情溶推,我是刑警寧澤徊件,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站蒜危,受9級(jí)特大地震影響虱痕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辐赞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一部翘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧响委,春花似錦新思、人聲如沸窖梁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纵刘。三九已至,卻和暖如春荸哟,著一層夾襖步出監(jiān)牢的瞬間假哎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工敲茄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留位谋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓堰燎,卻偏偏與公主長(zhǎng)得像掏父,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秆剪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理赊淑,服務(wù)發(fā)現(xiàn),斷路器仅讽,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題陶缺。內(nèi)存泄漏...
    _痞子閱讀 1,636評(píng)論 0 8
  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了洁灵,簡(jiǎn)單粗俗的講饱岸,...
    宇宙只有巴掌大閱讀 2,363評(píng)論 0 12
  • ###集合類泄漏 集合類如果僅僅有添加元素的方法,而沒(méi)有相應(yīng)的刪除機(jī)制徽千,導(dǎo)致內(nèi)存被占用苫费。如果這個(gè)集合類是全局性的變...
    RunningTeemo閱讀 575評(píng)論 0 0
  • 【導(dǎo)圖解說(shuō)】 《李威傳》整合了新書(shū)里所有有關(guān)李威個(gè)人經(jīng)歷的資料,終成此圖双抽。 新書(shū)畫(huà)了很多遍百框,這次從新的角度來(lái)繪制,...
    焦典I世界記憶大師閱讀 401評(píng)論 7 1