一次不講武德的 Android 線上 OOM 的排查過程

作者:王晨彥

開篇

一天,后臺(tái)統(tǒng)計(jì)到線上有大量 OOM 崩潰犹赖,小王收到老板的緊急指令青灼,立即排查!

小王心想姐浮,這還不簡單,待我看看崩潰堆棧葬馋,分分鐘解決卖鲤。

于是小王不慌不忙的打開崩潰后臺(tái),一看傻眼了畴嘶,同樣的 OOM蛋逾,卻有幾十種不同的堆棧,大到創(chuàng)建 View窗悯,小到 new 一個(gè) String区匣。

小王差點(diǎn)罵了出來:這 OOM 不講武德啊蒋院!

罵完之后亏钩,還是得解決問題啊,否則怎么面對(duì)老板啊欺旧。

心路歷程

正郁悶著姑丑,小王突然想起曾經(jīng)看過性能優(yōu)化的文章,里面介紹了 Android Studio 中集成的 Profiler 可以分析 APP 內(nèi)存辞友。

既然堆椪ぐВ看不出什么問題,那就只能照著文章的方法称龙,碰碰運(yùn)氣了昌屉。

于是小王點(diǎn)開了 IDE 底部那個(gè)毫不起眼的「Profiler」面板,映入眼簾的是:

小王一眼就看到了 MEMORY 欄茵瀑,這不就是內(nèi)存使用嘛间驮。

嗯,數(shù)據(jù)倒是挺全马昨,可是怎么知道哪里導(dǎo)致 OOM 了啊竞帽,小王又開始懷疑人生了…

“放著不動(dòng)肯定看不出什么啊,內(nèi)存是動(dòng)態(tài)申請(qǐng)的嘛鸿捧∫俾ǎ”

小王心想,既然這么多 OOM匙奴,那么肯定是 APP 內(nèi)的常用頁面導(dǎo)致的堆巧,于是小王開始一邊來回切換常用頁面,一邊觀察內(nèi)存走勢(shì)。

經(jīng)過多次嘗試谍肤,小王發(fā)現(xiàn)應(yīng)用的內(nèi)存占用確實(shí)在不斷升高啦租,即使手動(dòng) GC 之后,仍然居高不下荒揣。

小王想起面試寶典中「無法被 GC 回收的對(duì)象篷角,會(huì)導(dǎo)致內(nèi)存泄露」,于是手動(dòng)點(diǎn)了下 GC系任,避免數(shù)據(jù)不準(zhǔn)確恳蹲。

Java 堆從 15.7MB 漲到 19.3MB,好像問題不大俩滥,而 Native 就離譜了嘉蕾,好家伙,竟然從 56.1MB 漲到了 97.5MB霜旧,分分鐘就漲了 40MB+错忱。

小王喜出望外,終于發(fā)現(xiàn)內(nèi)存問題了颁糟!看來平時(shí)摸魚的時(shí)候多看看文章真是沒壞處啊航背。

可是喉悴,就算知道內(nèi)存不正常棱貌,但還是不能定位是哪段代碼導(dǎo)致了…

小王平復(fù)了一下心情,繼續(xù)觀察規(guī)律箕肃,終于發(fā)現(xiàn)婚脱,每次從A頁面跳轉(zhuǎn)出去,內(nèi)存都會(huì)增加幾M勺像,而且 GC 無法回收障贸,那肯定是這個(gè)頁面有問題了!

于是小王罵罵咧咧的開始閱讀這個(gè)頁面的代碼吟宦,希望能夠發(fā)現(xiàn)內(nèi)存泄露的元兇篮洁。心里嘀咕著,讓我看看是哪個(gè) ** 寫出了內(nèi)存泄露的代碼殃姓。

小王逐字逐句看完了代碼:可是并沒有什么問題啊袁波,就是一個(gè)普通的列表頁,還是用 RecyclerView 實(shí)現(xiàn)的蜗侈,沒啥毛病啊篷牌。

這下又把小王難住了,小王心想踏幻,不能在黎明前倒下啊枷颊,于是又想起文章中關(guān)于 Profiler 的介紹,可以使用 Dump 功能方便的查看當(dāng)前的內(nèi)存快照,興許能發(fā)現(xiàn)什么端倪呢夭苗。

好家伙信卡,原來是 Bitmap 占了這么大內(nèi)存,于是小王又想起面試寶典听诸。

Android 2.3.3(API level 10) 和更早的版本坐求,Bitmap 對(duì)象和對(duì)象里對(duì)應(yīng)的像素?cái)?shù)據(jù)是分開存儲(chǔ)的,Bitmap 存在虛擬機(jī)的堆里晌梨,而像素?cái)?shù)據(jù)存儲(chǔ)在 Native 內(nèi)存里桥嗤。

從 Android 3.0(API level 11) 到 Android 7.1(API level 25),Bitmap 對(duì)象及其像素?cái)?shù)據(jù)都存儲(chǔ)在虛擬機(jī)的堆里仔蝌。

從 Android 8.0(API level 26) 開始泛领,Bitmap 對(duì)象存儲(chǔ)在虛擬機(jī)的堆里,而對(duì)應(yīng)的像素?cái)?shù)據(jù)存儲(chǔ)在 Native 堆里敛惊。

小王測試的手機(jī)是 Android 10渊鞋,Bitmap 數(shù)據(jù)存儲(chǔ)在 Native 堆,所以基本上可以確定就是 Bitmap 導(dǎo)致內(nèi)存泄露了瞧挤。雖然又前進(jìn)了一大步锡宋,但還是找不到原因。

小王發(fā)現(xiàn)特恬,點(diǎn)擊對(duì)象执俩,可以查看所有實(shí)例的引用鏈,這下可把小王高興壞了癌刽,而且小王還發(fā)現(xiàn)了一個(gè)非骋凼祝可疑的引用鏈。

這不是 Coil 的 Memory Cache 嘛显拜,可是這里明明是有緩存的嘛衡奥,怎么還會(huì)泄露,難不成是這個(gè)開源庫有 bug远荠?

https://github.com/coil-kt/coil

小王懷著忐忑的心情打開了 RealMemoryCache 這個(gè)類矮固。

這不就是一個(gè)基于 LRU 實(shí)現(xiàn)的內(nèi)存緩存嘛,乍一看好像沒什么毛病譬淳。

沒時(shí)間仔細(xì)研究了档址,小王心想,先看看開源社區(qū)有沒有人反饋過這個(gè)問題瘦赫,小王過濾了一下包含 "memory leak" 關(guān)鍵字的 issue辰晕。

果然有一個(gè) PR 的標(biāo)題非常接近 Fix memory leak if request is started on detached view.

https://github.com/coil-kt/coil/pull/518

看起來問題已經(jīng)被修復(fù)且已經(jīng)發(fā)布了新版本,于是小王立馬升級(jí)版本再次測試确虱,果然沒有泄露了含友。

于是立馬提交代碼,興沖沖的去找老板炫耀了!>轿省辆童!

追根溯源

回過頭來,小王心想惠赫,作為一個(gè)“有上進(jìn)心”的程序員把鉴,我得看看是什么原因?qū)е碌男孤栋 ?/p>

于是再次打開 PR,在諸多改動(dòng)中儿咱,終于找到一個(gè)真正的代碼改動(dòng)庭砍,其他都是測試用例。

小王不禁感慨混埠,歪果仁就是專業(yè)呀怠缸,改了兩行代碼就要寫一堆測試用例。

小王終于弄清了導(dǎo)致泄露的原因钳宪,原來是在快速切換頁面時(shí)揭北,有時(shí)頁面已經(jīng)銷毀了,才開始加載圖片吏颖,此時(shí) Coil 會(huì)把這個(gè) View 對(duì)象保存起來搔体,等待 View detach 的時(shí)候釋放,然而此時(shí) View 已經(jīng)是 detach 的狀態(tài)了半醉,因此永遠(yuǎn)不會(huì)被釋放了疚俱,而 Bitmap 又被 View 持有,而我們都知道 Bitmap 是內(nèi)存占用大戶奉呛,因此就出現(xiàn)了上面 Bitmap 占用大量內(nèi)存的情況计螺。

而解決方案就是再判斷一下 View 是否已經(jīng) Detach夯尽,是的話就直接釋放了瞧壮,避免造成泄露。

最后小王高高興興的下班了匙握。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咆槽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子圈纺,更是在濱河造成了極大的恐慌秦忿,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛾娶,死亡現(xiàn)場離奇詭異灯谣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蛔琅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門胎许,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事辜窑」呈觯” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵穆碎,是天一觀的道長牙勘。 經(jīng)常有香客問我,道長所禀,這世上最難降的妖魔是什么方面? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮色徘,結(jié)果婚禮上葡幸,老公的妹妹穿的比我還像新娘。我一直安慰自己贺氓,他們只是感情好蔚叨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辙培,像睡著了一般蔑水。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扬蕊,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天搀别,我揣著相機(jī)與錄音,去河邊找鬼尾抑。 笑死歇父,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的再愈。 我是一名探鬼主播榜苫,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼翎冲!你這毒婦竟也來了垂睬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤抗悍,失蹤者是張志新(化名)和其女友劉穎驹饺,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缴渊,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赏壹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衔沼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝌借。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡田柔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骨望,到底是詐尸還是另有隱情硬爆,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布擎鸠,位于F島的核電站缀磕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏劣光。R本人自食惡果不足惜袜蚕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绢涡。 院中可真熱鬧牲剃,春花似錦、人聲如沸雄可。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽数苫。三九已至聪舒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虐急,已是汗流浹背箱残。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留止吁,地道東北人被辑。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像敬惦,于是被迫代替她去往敵國和親盼理。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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