LeakCanary內(nèi)存泄露檢測原理

LeakCanary代碼量比較多兄朋,閱讀源碼容易把人繞暈,提取主干代碼,精簡后的代碼只有200行翔悠,看完這200行代碼业崖,LeakCanary檢測內(nèi)存泄露原理應(yīng)該就清楚了,回頭再看LeakCanary完整源碼就不再神秘。

精簡后的源代碼已發(fā)布在Github上:https://github.com/andev009/LearnLeakCanary

LeakCanary是安卓開發(fā)中分析內(nèi)存泄露的工具蓄愁。源代碼內(nèi)容很多双炕,總結(jié)起來主要分三個(gè)部分:1.檢測泄露,2撮抓,分析泄露妇斤,3,通知泄露丹拯。分析泄露用到了HAHA這個(gè)工具站超,有興趣的可以單獨(dú)分析,通知泄露乖酬,利用安卓自身消息機(jī)制死相,Service, Notification, Activity這些,按流程走就行了咬像。本文分析內(nèi)存泄露檢測這塊算撮,這塊才是LeakCanary原理的核心。

LeakCanary檢測內(nèi)存泄露分兩個(gè)部分:
1.監(jiān)聽Activity生命周期施掏,在Activity銷毀的時(shí)候通知LeakCanary钮惠。
2.內(nèi)存泄露檢測.

監(jiān)聽Activity生命周期

public class LearnLeakCanaryApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        ActivityRefWatcher.install(this, new RefWatcher());
    }
}

首先看入口,在Application完成注冊(cè)工作七芭,和LeakCanary源碼注冊(cè)不一樣素挽,用的ActivityRefWatcher完成注冊(cè),其實(shí)LeakCanary也差不多狸驳,不過多了各種配置预明。

注冊(cè)主要做兩件事,一是Application綁定Activity生命周期,Activity銷毀的時(shí)候都能監(jiān)聽到耙箍。二是new了個(gè)RefWatcher對(duì)象撰糠,RefWatcher就做了一件事,檢測泄露辩昆,如果泄露就捕捉給HAHA處理阅酪。這樣看,這一句代碼就完成了兩件重要的工作汁针。

ActivityRefWatcher注冊(cè)時(shí)最后的調(diào)用watchActivities术辐,在這個(gè)函數(shù)里完成Application和生命周期的監(jiān)聽注冊(cè)。

public static void install(Application application, RefWatcher refWatcher) {
        new ActivityRefWatcher(application, refWatcher).watchActivities();
    }
public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
    }

lifecycleCallbacks就是生命周期回調(diào)施无,Activity銷毀時(shí)辉词,會(huì)被Application監(jiān)聽,然后走onActivityDestroyed回調(diào)猾骡。在onActivityDestroyed里瑞躺,把自己交給RefWatcher敷搪,讓RefWatcher去檢測自己是否真的被回收。

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) {
                    Log.d(TAG, activity.getLocalClassName() + " onActivityDestroyed");

                    ActivityRefWatcher.this.onActivityDestroyed(activity);
                }
            };

內(nèi)存泄露判斷

內(nèi)存泄露判斷主要是用到了WeakReference和ReferenceQueue幢哨,他們倆的關(guān)系很簡單赡勘,弱引用對(duì)象回收了,弱引用對(duì)象就會(huì)在ReferenceQueue里捞镰,如果ReferenceQueue里沒有狮含,就說明可能泄露。至于為什么會(huì)這樣曼振,看Reference源碼這里入隊(duì)列這段注釋几迄。

/**
     * Adds this reference object to the queue with which it is registered,
     * if any.
     *
     * <p> This method is invoked only by Java code; when the garbage collector
     * enqueues references it does so directly, without invoking this method.
     *
     * @return   <code>true</code> if this reference object was successfully
     *           enqueued; <code>false</code> if it was already enqueued or if
     *           it was not registered with a queue when it was created
     */
    public boolean enqueue() {
       return queue != null && queue.enqueue(this);
    }

上面說可能泄露是因?yàn)镚C不一定及時(shí),所以LeakCanary會(huì)再調(diào)一次GC冰评,然后再檢測ReferenceQueue是否存在回收的對(duì)象映胁。如果這次還沒有就是泄露了,后面的邏輯就是給HAHA分析甲雅,Notification通知解孙。

void ensureGone(final KeyedWeakReference reference, String referenceName, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

        removeWeaklyReachableReferences();

        if (gone(reference)) {
            return;
        }
        gcTrigger.runGc();
        removeWeaklyReachableReferences();
        if (!gone(reference)) {
            //do HeapDump, HeapAnalyzer
            Log.d("RefWatcher", Thread.currentThread().getName());
            Log.d("RefWatcher",referenceName + " leaked!");
        }
        return;
    }

整個(gè)流程很簡單,精簡后的代碼只有不到200行抛人。需要說明的是弛姜,LeakCanary在新線程里檢測,這里為了代碼簡單妖枚,用Handle postDelayed 5秒后在主線程檢測廷臼,原理都一樣。跑Demo時(shí)绝页,看ensureGone里泄露時(shí)的Log荠商。SecondActivity,ThirdActivity之所以內(nèi)存泄露是因?yàn)槔锩娴膬?nèi)部類持有外部引用续誉。仔細(xì)看看Log,應(yīng)該就理解原理了莱没。


Log.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酷鸦,隨后出現(xiàn)的幾起案子饰躲,更是在濱河造成了極大的恐慌,老刑警劉巖臼隔,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘹裂,死亡現(xiàn)場離奇詭異,居然都是意外死亡躬翁,警方通過查閱死者的電腦和手機(jī)焦蘑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門盯拱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盒发,“玉大人例嘱,你說我怎么就攤上這事∧ⅲ” “怎么了拼卵?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛮艰。 經(jīng)常有香客問我腋腮,道長,這世上最難降的妖魔是什么壤蚜? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任即寡,我火速辦了婚禮,結(jié)果婚禮上袜刷,老公的妹妹穿的比我還像新娘聪富。我一直安慰自己,他們只是感情好著蟹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布墩蔓。 她就那樣靜靜地躺著,像睡著了一般萧豆。 火紅的嫁衣襯著肌膚如雪奸披。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天涮雷,我揣著相機(jī)與錄音阵面,去河邊找鬼。 笑死洪鸭,一個(gè)胖子當(dāng)著我的面吹牛膜钓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播卿嘲,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颂斜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拾枣?” 一聲冷哼從身側(cè)響起沃疮,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梅肤,沒想到半個(gè)月后司蔬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姨蝴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年俊啼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片左医。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡授帕,死狀恐怖同木,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跛十,我是刑警寧澤彤路,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站芥映,受9級(jí)特大地震影響洲尊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奈偏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一坞嘀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惊来,春花似錦姆吭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厘擂,卻和暖如春昆淡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刽严。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工昂灵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舞萄。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓眨补,卻偏偏與公主長得像,于是被迫代替她去往敵國和親倒脓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撑螺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355