本文為LeakCanary: Detect all memory leaks!的翻譯。原文在: https://corner.squareup.com/2015/05/leak-canary.html
本文為轉(zhuǎn)載腌乡。
java.lang.OutOfMemoryError at android.graphics.Bitmap.nativeCreate(Bitmap.java:-2) at android.graphics.Bitmap.createBitmap(Bitmap.java:689) at com.squareup.ui.SignView.createSignatureBitmap(SignView.java:121)
誰(shuí)也不會(huì)喜歡 OutOfMemoryError
在 Square Register 中, 在簽名頁(yè)面盟劫,我們把客戶(hù)的簽名畫(huà)在 bitmap cache 上。 這個(gè) bitmap 的尺寸幾乎和屏幕的尺寸一樣大与纽,在創(chuàng)建這個(gè) bitmap 對(duì)象時(shí)侣签,經(jīng)常會(huì)引發(fā) OutOfMemoryError
,簡(jiǎn)稱(chēng)OOM
急迂。
![](https://corner.squareup.com/images/leakcanary/signature.png)
當(dāng)時(shí)影所,我們嘗試過(guò)一些解決方案,但都沒(méi)解決問(wèn)題
使用 Bitmap.Config.ALPHA_8 因?yàn)榱潘椋灻麅H有黑色猴娩。
捕捉 OutOfMemoryError
, 嘗試 GC 并重試(受 GCUtils 啟發(fā))。
我們沒(méi)想過(guò)在 Java heap 內(nèi)存之外創(chuàng)建 bitmap 勺阐【碇校苦逼的我們,那會(huì) Fresco 還不存在渊抽。
路子走錯(cuò)了
其實(shí) bitmap 的尺寸不是真正的問(wèn)題蟆豫,當(dāng)內(nèi)存吃緊的時(shí)候,到處都有可能引發(fā) OO腰吟。在創(chuàng)建大對(duì)象无埃,比如 bitmap 的時(shí)候,更有可能發(fā)生毛雇。OOM 只是一個(gè)表象嫉称,更深層次的問(wèn)題可能是: 內(nèi)存泄露。
什么是內(nèi)存泄露
一些對(duì)象有著有限的生命周期灵疮。當(dāng)這些對(duì)象所要做的事情完成了织阅,我們希望他們會(huì)被回收掉。但是如果有一系列對(duì)這個(gè)對(duì)象的引用震捣,那么在我們期待這個(gè)對(duì)象生命周期結(jié)束的時(shí)候被收回的時(shí)候荔棉,它是不會(huì)被回收的。它還會(huì)占用內(nèi)存蒿赢,這就造成了內(nèi)存泄露润樱。持續(xù)累加,內(nèi)存很快被耗盡羡棵。
比如壹若,當(dāng) Activity.onDestroy
被調(diào)用之后,activity 以及它涉及到的 view 和相關(guān)的 bitmap 都應(yīng)該被回收。但是店展,如果有一個(gè)后臺(tái)線(xiàn)程持有這個(gè) activity 的引用月褥,那么 activity 對(duì)應(yīng)的內(nèi)存就不能被回收台妆。這最終將會(huì)導(dǎo)致內(nèi)存耗盡,然后因?yàn)?OOM 而 crash。
對(duì)戰(zhàn)內(nèi)存泄露
排查內(nèi)存泄露是一個(gè)全手工的過(guò)程惑芭,這在 Raizlabs 的 Wrangling Dalvik 系列文章中有詳細(xì)描述盅安。
以下幾個(gè)關(guān)鍵步驟:
通過(guò) Bugsnag, Crashlytics 或者 Developer Console 等統(tǒng)計(jì)平臺(tái)面殖,了解 OutOfMemoryError
情況震缭。
重現(xiàn)問(wèn)題。為了重現(xiàn)問(wèn)題席怪,機(jī)型非常重要应闯,因?yàn)橐恍﹩?wèn)題只在特定的設(shè)備上會(huì)出現(xiàn)。為了找到特定的機(jī)型挂捻,你需要想盡一切辦法碉纺,你可能需要去買(mǎi),去借刻撒,甚至去偷骨田。 當(dāng)然,為了確定復(fù)現(xiàn)步驟声怔,你需要一遍一遍地去嘗試态贤。一切都是非常原始和粗暴的。
在發(fā)生內(nèi)存泄露的時(shí)候醋火,把內(nèi)存 Dump 出來(lái)悠汽。具體看這里。
然后芥驳,你需要在 MAT 或者 YourKit 之類(lèi)的內(nèi)存分析工具中反復(fù)查看柿冲,找到那些原本該被回收掉的對(duì)象。
計(jì)算這個(gè)對(duì)象到 GC roots 的最短強(qiáng)引用路徑兆旬。
確定引用路徑中的哪個(gè)引用是不該有的假抄,然后修復(fù)問(wèn)題。
很復(fù)雜對(duì)吧丽猬?
如果有一個(gè)類(lèi)庫(kù)能在發(fā)生 OOM 之前把這些事情全部都搞定宿饱,然后你只要修復(fù)這些問(wèn)題就好了,豈不妙哉脚祟!
LeakCanary
LeakCanary 是一個(gè)檢測(cè)內(nèi)存泄露的開(kāi)源類(lèi)庫(kù)谬以。你可以在 debug 包種輕松檢測(cè)內(nèi)存泄露。
先看一個(gè)例子:
class Cat {}class Box { Cat hiddenCat;}class Docker { // 靜態(tài)變量由桌,將不會(huì)被回收为黎,除非加載 Docker 類(lèi)的 ClassLoader 被回收胡陪。 static Box container;}// ...Box box = new Box();// 薛定諤之貓Cat schrodingerCat = new Cat();box.hiddenCat = schrodingerCat;Docker.container = box;
創(chuàng)建一個(gè)RefWatcher
,監(jiān)控對(duì)象引用情況碍舍。
// 我們期待薛定諤之貓很快就會(huì)消失(或者不消失),我們監(jiān)控一下refWatcher.watch(schrodingerCat);
當(dāng)發(fā)現(xiàn)有內(nèi)存泄露的時(shí)候邑雅,你會(huì)看到一個(gè)很漂亮的 leak trace 報(bào)告:
GC ROOT static Docker.container
references Box.hiddenCat
leaks Cat instance
我們知道片橡,你很忙,每天都有一大堆需求淮野。所以我們把這個(gè)事情弄得很簡(jiǎn)單捧书,你只需要添加一行代碼就行了。然后 LeakCanary 就會(huì)自動(dòng)偵測(cè) activity 的內(nèi)存泄露了骤星。
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); }}
然后你會(huì)在通知欄看到這樣很漂亮的一個(gè)界面:
![](https://corner.squareup.com/images/leakcanary/leaktrace.png)
結(jié)論
使用 LeakCanary 之后经瓷,我們修復(fù)了我們 APP 中相當(dāng)多的內(nèi)存泄露。我們甚至發(fā)現(xiàn)了 Android SDK 中的一些內(nèi)存泄露問(wèn)題洞难。
結(jié)果是驚艷的舆吮,我們減少了 94% 的由 OOM 導(dǎo)致的 crash。
![](https://corner.squareup.com/images/leakcanary/oom_rate.png)
如果你也想消滅 OOM crash队贱,那還猶豫什么色冀,趕快使用 LeakCanary
相關(guān)鏈接:
LeakCanary 中文使用說(shuō)明
一個(gè)非常簡(jiǎn)單的 LeakCanary demo: https://github.com/liaohuqiu/leakcanary-demo