Android內(nèi)存泄漏分析(MAT)

本文是使用Eclipse Memory Analyzer Tool (MAT)進(jìn)行內(nèi)存泄漏分析的筆記舍杜。遇到大大小小的內(nèi)存泄漏,都可以通過MAT分析出來桅滋。

安裝

MAT官網(wǎng) https://www.eclipse.org/mat/

To install the Memory Analyzer into an Eclipse IDE use the update site URL provided below. The Memory Analyzer (Chart) feature is optional. The chart feature requires the BIRT Chart Engine (Version 2.3.0 or greater).

總體有3種方式精拟。

  1. 直接下載獨立安裝包
  2. 下載Eclipse插件包,作為Eclipse插件安裝(Archived Update)
  3. 在線安裝Eclipse插件包虱歪,同樣作為插件安裝(Update Site地址)

在Mac下面,單獨安裝失敗栅表,使用Eclipse自帶插件服務(wù)器安裝失斔癖伞(太慢),改為UpdateSize的地址后怪瓶,在線安裝成功萧落。

分析

MAT的文件后綴是hprof,從AndroidStudio中得到的hprof需要經(jīng)過轉(zhuǎn)換(網(wǎng)上有教程)洗贰。這里廢話不多說找岖,直接進(jìn)入主題。

MAT界面:

  • Overview(概述)
  • Histogram(直方圖)
  • Leak Suspects(泄漏猜測)

Overview

QQ20170929-154814@2x.png

Leak Suspects

QQ20170929-155719@2x.png

大部分LeakSuspects都會檢測到一些問題敛滋。下圖是一個內(nèi)存泄漏到檢測信息许布。


QQ20170929-155931@2x.png

點擊Details ?查看詳情,得到一個引用鏈绎晃。

QQ20170929-160233@2x.png

這張引用鏈表示蜜唾,一張大圖片沒有被釋放 -> 被Drawable引用 -> 被CameraPopWindow引用 -> 被CBarrageView引用 -> 即CBarrageView在退出的時候沒能被釋放杂曲,出現(xiàn)了內(nèi)存泄漏。

再往下看袁余,CBarrageView沒能被釋放擎勘,是因為被一個叫HandlerAction引用,這個HandlerAction是在一個數(shù)組中颖榜,這個數(shù)組由ViewRootImpl.RunQueue持有棚饵,最后RunQueue是被Thread引用了。

查看最后的Thread掩完,選擇ListObjects - with incoming references表示查看引用了這個Thread的對象:

QQ20171127-094245@2x.png

可以看到噪漾,Thread作為mUiThread被DisplayActivity引用著。布局層次上藤为,DisplayActivity -> EditAndPreviewActivity(CBarrageView容器)怪与,所以,CBarrageView跨越了它的生命周期缅疟,是因為DisplayActivity里的mUiThread在持有它分别,為什么CBarrageView會被DisplayActivity的mUiThread持有呢?

此時存淫,通常有兩種方式確認(rèn)泄漏的位置耘斩。

方式1,上網(wǎng)查關(guān)鍵字桅咆。

QQ20171127-095205@2x.png

可以看到括授,大致和View的post相關(guān)⊙冶可以查看CBarrageView里post相關(guān)是否使用得當(dāng)荚虚。

方式2,繼續(xù)跟進(jìn)源碼籍茧。

查看源碼:跟進(jìn)Activity源碼版述。

private Thread mUiThread;

mUiThread = Thread.currentThread();  // 主線程

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

猜想可能和post相關(guān),想起CBarrageView里面使用了postDelayed()寞冯,

    public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
        return true;
    }

這里渴析,ViewRootImpl,RunQueue和內(nèi)存泄漏檢測的引用鏈很接近吮龄。所以可以繼續(xù)猜想俭茧,是由于CBarrageView的postDelayed導(dǎo)致的。

跟到CBarrageView內(nèi)部漓帚,可以發(fā)現(xiàn)下面的代碼母债,Runnable并沒有主動釋放!這就是泄漏的原因:

    private void initView() {
        postDelayed(new Runnable() {
            @Override
            public void run() {
                checkRowIdle();
                postDelayed(this, 50);
            }
        }, 50);
    }

這里為什么泄漏呢胰默?

  1. 匿名內(nèi)部類Runnable持有外部類CBarrageView的引用
  2. Runnable超出當(dāng)前類的生命周期(Runnable是丟在主線程的消息隊列场斑,這個是View的postDelayed接口的問題了漓踢,沒有做到自動釋放)
  3. 當(dāng)前類生命周期結(jié)束的時候,沒有主動釋放Runnable(CBarrageView生命周期結(jié)束漏隐,但主線程生命周期還健在)

本來假想View釋放的時候會把附帶的Runnable釋放掉喧半,但這個Runnable并不是依附到當(dāng)前View,而是主線程青责。所以不確定的地方切記使用最安全的方式挺据,否則就相當(dāng)于挖了個坑。

修改后:

   private void initView() {
        postDelayed(mCheckRowIdleTask, 50);
    }

    private Runnable mCheckRowIdleTask = new Runnable() {
        @Override
        public void run() {
            checkRowIdle();
            postDelayed(this, 50);
        }
    };

    public void release() {
        removeCallbacks(mCheckRowIdleTask);
    }

Histogram

QQ20170929-155630@2x.png

Dominator Tree
顯示大對象列表

QQ20171127-111424@2x.png

內(nèi)存泄漏檢測與OOM定位

  1. 如果是OOM崩潰脖隶,可以直接拿到對應(yīng)hprof文件進(jìn)行分析扁耐,通常通過LeakSuspects就可以定位到OOM的位置。

  2. 內(nèi)存泄漏的檢測步驟:
    2.1 確定要檢測的頁面(功能/模塊)
    2.2 進(jìn)入對應(yīng)的頁面产阱,操作
    2.3 退出頁面婉称,主動執(zhí)行一次GC(不執(zhí)行也行)
    2.4 抓取hprof文件使用MAT進(jìn)行分析。
    2.5 跳到Histogram界面构蹬,使用過濾器(圖中灰色<Regex>位置)找到對應(yīng)的頁面王暗。沒有找到,恭喜庄敛,生命周期正常俗壹,沒有內(nèi)存泄漏。找到了藻烤,則根據(jù)下面的步驟繼續(xù)定位绷雏。

MAT定位

QQ20171127-113347@2x.png

最重要的幾個選項,優(yōu)先級從高到低:

  1. Path To GC Roots
    列出當(dāng)前對象到GCRoot的引用鏈(自底向上)


    QQ20171127-114404@2x.png

自底向上怖亭,從當(dāng)前對象到GCRoot涎显。很容易看出,當(dāng)前對象沒能被釋放兴猩,是因為ImageView引用著棺禾,繼而被CBarrageView引用著,繼而被Thread的RunQueue引用著峭跳。

  1. Merge Shortest Paths to GC Roots
    列出當(dāng)前對象到GCRoot的引用鏈(自頂向下)


    QQ20171127-113804@2x.png

自頂向下,從GCRoot到當(dāng)前對象缺前。如果要看CBarrageView為何沒能被釋放蛀醉,則從下往上看。如果要看當(dāng)前對象真正被引用的地方衅码,就是最底部的ImageView拯刁。換句話,對象之所以沒有被釋放逝段,是因為被ImageView引用著垛玻,被CBarrageView引用著割捅,被更上層的Thread引用著。

  1. With incoming references 和 With outgoing references
    With incoming references 是列出引用當(dāng)前對象的對象帚桩。
    With outgoing references 是列出當(dāng)前對象引用的對象亿驾。

如果當(dāng)前對象有泄漏,則只需看 incoming references即可账嚎,即找出它被什么對象引用了導(dǎo)致了生命周期異常莫瞬。根本無需關(guān)心它引用了哪些對象!郭蕉!

在內(nèi)存泄漏方面疼邀,outgoing references并沒有什么用,切記不要點召锈,只會混淆視聽

  1. List Objects 和 Show Objects By Class
    List Objects是按照類的實例(對象)來顯示旁振。
    Show Objects By Class是按照類名來顯示。
QQ20171127-115126@2x.png
QQ20171127-115109@2x.png

差異自行感知涨岁。

  1. references的過濾器
    5.1 with all references 即所有引用都顯示出來
    5.2 exclude weak reference 即不顯示弱引用
    5.3 exclude soft reference 即不顯示軟引用
    5.4 exclude phantom reference 即不顯示幽靈引用

由于weak/soft/phantom引用都可以被GC回收拐袜,所以三者都可以不顯示。通常使用exclude weak/soft reference卵惦。(weak是引用可回收時立即被回收變?yōu)閚ull阻肿,soft是引用可回收但會等到內(nèi)存不足時才回收,phantom是用來跟蹤引用釋放用的沮尿,本身不會產(chǎn)生強(qiáng)引用)

QQ20171127-113640@2x.png

回到實例

如上面所述丛塌,懷疑CBarrageView有內(nèi)存泄漏,在退出了CBarrageView后捕獲hprof文件畜疾。

  1. 選擇Histogram(Dominator Tree可以忽略了赴邻,后面會說)

  2. 按照包名過濾:com.xxx.xxx


    QQ20171127-153205@2x.png
  3. 如圖,選擇含有CBarrageView(如CBarrageView$CRecycleBin)啡捶,或者在CBarrageView里引用的對象(如CBarrageItem)都可以姥敛。因為CBarrageView沒有釋放,其內(nèi)部引用的對象也不會釋放瞎暑,最后到GCRoot的引用鏈?zhǔn)且恢碌耐病_@里要注意不要使用對象數(shù)為0的來分析,因為這種無法生成引用鏈了赌。同時墨榄,建議使用更底層的對象,因為當(dāng)前對象不能釋放很可能是由于內(nèi)部b(見另一個案例)勿她。

  4. 右鍵,選擇Merge Shortest Paths to GC Roots,過濾掉weak和soft引用之剧,然后就生成下面的引用鏈(自頂向下)郭卫。

    QQ20171127-154629@2x.png

注:自頂向下,黑色部分是變量背稼,變量的類型是上一條贰军。如圖,localValues變量的類型并不是Values雇庙,而是上面的Thread谓形;mActions變量并不是ArrayList而是上面那條RunQueue。

  1. 如果習(xí)慣自底向上分析疆前,可以按照步驟3再選擇一個對象寒跳,右鍵選擇Path To GC Roots,同樣過濾掉weak和soft引用竹椒,然后就生成下面的引用鏈(自底向上)童太。
    QQ20171127-154647@2x.png

注:自底向上,黑體部分是變量胸完,變量的類型緊隨其后书释,和自頂向下不同!如圖赊窥,mActions變量類型是RunQueue爆惧,是下面table變量的一個元素。

  1. 過濾掉weak和soft引用可以減少不必要的分析锨能,因為誤分析了weak和soft的引用其實一點幫助都沒有扯再,只會浪費時間。

  2. 根據(jù)引用鏈猜想或定位問題址遇∠ㄗ瑁可以通過源碼,或者通過搜索關(guān)鍵字倔约。

最后秃殉,回答為什么不使用Dominator Tree。因為這個界面并不能百分百生成引用鏈浸剩。如下圖钾军,CBarrageView相關(guān)的對象,幾乎有一半沒能正確生成引用鏈绢要。這會誤導(dǎo)巧颈,模塊沒有發(fā)生內(nèi)存泄漏,所以不要再使用Dominator Tree界面來進(jìn)行內(nèi)存泄漏分析袖扛。這個界面就只是單純看大對象就算了!!


QQ20171127-160738@2x.png
QQ20171127-154131@2x.png

一些內(nèi)存泄漏例子

  1. android.view.ViewRootImpl$RunQueue
    使用了View的post或postDelayed沒有進(jìn)行Runnable的主動釋放蛆封。導(dǎo)致Runnable泄漏到主線程唇礁。

  2. android.app.LoadedApk$ReceiverDispatcher$InnerReceiver
    mDispatcher java.lang.ref.WeakReference
    mContext android.app.LoadedApk$ReceiverDispatcher
    使用了廣播,注冊了沒有進(jìn)行反注冊惨篱,導(dǎo)致泄漏到廣播分發(fā)隊列中盏筐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砸讳,隨后出現(xiàn)的幾起案子琢融,更是在濱河造成了極大的恐慌,老刑警劉巖簿寂,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漾抬,死亡現(xiàn)場離奇詭異,居然都是意外死亡常遂,警方通過查閱死者的電腦和手機(jī)纳令,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來克胳,“玉大人平绩,你說我怎么就攤上這事∧恚” “怎么了捏雌?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長笆搓。 經(jīng)常有香客問我性湿,道長,這世上最難降的妖魔是什么砚作? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任窘奏,我火速辦了婚禮,結(jié)果婚禮上葫录,老公的妹妹穿的比我還像新娘着裹。我一直安慰自己,他們只是感情好米同,可當(dāng)我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布骇扇。 她就那樣靜靜地躺著,像睡著了一般面粮。 火紅的嫁衣襯著肌膚如雪少孝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天熬苍,我揣著相機(jī)與錄音稍走,去河邊找鬼袁翁。 笑死,一個胖子當(dāng)著我的面吹牛婿脸,可吹牛的內(nèi)容都是我干的粱胜。 我是一名探鬼主播,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼狐树,長吁一口氣:“原來是場噩夢啊……” “哼焙压!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抑钟,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤涯曲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后在塔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幻件,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年心俗,在試婚紗的時候發(fā)現(xiàn)自己被綠了傲武。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡城榛,死狀恐怖揪利,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狠持,我是刑警寧澤疟位,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站喘垂,受9級特大地震影響甜刻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜正勒,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一得院、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧章贞,春花似錦祥绞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至朴皆,卻和暖如春帕识,著一層夾襖步出監(jiān)牢的瞬間渡冻,已是汗流浹背帽借。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留凝垛,地道東北人。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溃睹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,687評論 2 351

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