Android常見的內(nèi)存泄漏出現(xiàn)原因的分析及檢查辦法

什么是內(nèi)存泄漏?

內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放筹麸,造成系統(tǒng)內(nèi)存的浪費(fèi)鳄抒,導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果佛掖。

很多人會(huì)把內(nèi)存泄漏和內(nèi)存溢出混淆供常,其實(shí)兩者并不是同一個(gè)概念,但是兩者卻有非常重要的聯(lián)系偿枕,簡單來說大量的內(nèi)存泄漏就會(huì)導(dǎo)致內(nèi)存溢出璧瞬。下面貼出內(nèi)存溢出的概念。

內(nèi)存溢出:程序向系統(tǒng)申請(qǐng)的內(nèi)存空間超出了系統(tǒng)能給的渐夸。

比如內(nèi)存只能分配一個(gè)int類型嗤锉,我卻要塞給他一個(gè)long類型,系統(tǒng)就出現(xiàn)oom墓塌。簡單來說就是房子就那么大瘟忱,但是越來越多的人進(jìn)來,直到房子已經(jīng)裝不下這么多人苫幢,人還在往房子里面走访诱,就會(huì)導(dǎo)致房子越來越擁擠,直到將房子擠爆韩肝。

我們理解了內(nèi)存泄漏和內(nèi)存溢出的區(qū)別之后触菜。那么我們就來看看導(dǎo)致我們程序內(nèi)存泄漏的根本原因是什么。


暗中觀察一番之后哀峻,我們來看內(nèi)存泄漏出現(xiàn)的根本原因涡相。首先我們都知道java是有垃圾回收機(jī)制的。也就是我們常說的gc剩蟀。gc是可以自動(dòng)清除堆中我們不再使用的對(duì)象的催蝗。當(dāng)然了,在java中對(duì)象是通過引用來使用的育特。但是如果再也沒有引用指向?qū)ο蟮脑挶牛敲催@個(gè)對(duì)象就無從處理,無從調(diào)用。在java中我們稱這種對(duì)象為不可到達(dá)對(duì)象犬缨。簡單來說喳魏,此對(duì)象在內(nèi)存中的申請(qǐng)的空間我們無法回收,有對(duì)象的強(qiáng)引用遍尺,且沒有及時(shí)釋放截酷,進(jìn)而造成內(nèi)存單元一直被占用涮拗,浪費(fèi)空間乾戏,就可能造成內(nèi)存溢出!

下面我們總結(jié)一下安卓內(nèi)存泄漏出現(xiàn)的原因三热,常見內(nèi)存泄漏的匯總鼓择。


1.非靜態(tài)內(nèi)部類或者匿名內(nèi)部類隱式持有外部類對(duì)象。簡單來說當(dāng)非靜態(tài)內(nèi)部類的對(duì)象的生命周期比外部類對(duì)象生命周期長就漾,就會(huì)引起內(nèi)存泄漏呐能。安卓比較典型場景就是使用handler.

也就是說當(dāng)handler正在處理消息的時(shí)候,用戶退出activity,但是這個(gè)時(shí)候handler還在處理消息抑堡。導(dǎo)致activity無法被回收摆出。也就會(huì)發(fā)生內(nèi)存泄漏。

解決辦法:

1)將handler使用static修飾

2)handler通過弱引用的方式持有activity

3)在activity的ondestory生命周期中將handler中的消息置空

2.單例模式也會(huì)引起內(nèi)存泄漏首妖。我們使用單例模式是希望全局只有一個(gè)靜態(tài)變量偎漫,如果我們傳入了上下文的話,activity是間接繼承上下文的有缆。所以這個(gè)時(shí)候我們要是將activity退出象踊,應(yīng)該是回收activity的,但是單例模式還持有著它的引用棚壁,導(dǎo)致activity回收失敗杯矩,造成內(nèi)存泄漏。

解決辦法:

1)不管外面?zhèn)魅胧裁瓷舷挛男渫猓覀儐卫J嚼锩娑冀o它轉(zhuǎn)化為application的context史隆,這樣單例模式的生命周期就和應(yīng)用一樣長,避免了內(nèi)存泄漏曼验。

3.mvp框架引起內(nèi)存泄漏逆害。Mvp框架優(yōu)點(diǎn)很多,包括高度解耦蚣驼,代碼復(fù)讀性強(qiáng)等等魄幕,但是它也有缺點(diǎn)。缺點(diǎn)之一就是容易造成內(nèi)存泄漏颖杏。Presenter層持有著view的接口對(duì)象纯陨,model也很有可能擁有者presenter實(shí)例。所以當(dāng)activity銷毀的時(shí)候,model還在獲取著數(shù)據(jù)翼抠。Presenter也就一直持有著view對(duì)象咙轩。這條gc鏈不間斷,activity就無法正常的回收阴颖。

解決辦法:

1)在actity的ondestory方法中利用presenter層進(jìn)行資源釋放活喊,解除和view層的綁定,并且取消model層的網(wǎng)絡(luò)請(qǐng)求量愧。最后置空presenter層钾菊。

2)將presenter層轉(zhuǎn)化為弱引用去引用view對(duì)象

4.RxJava也會(huì)引起內(nèi)存泄漏。內(nèi)存泄漏產(chǎn)生的根本原因偎肃,當(dāng)一個(gè)對(duì)象處于可以被回收狀態(tài)時(shí)煞烫,卻因?yàn)樵搶?duì)象被其他暫時(shí)不可被回收的對(duì)象持有引用,而導(dǎo)致不能被回收累颂,如此一來滞详,該對(duì)象所占用的內(nèi)存被回收以作他用,這部分內(nèi)存就算是被泄露掉了紊馏。簡單來說料饥,就是該丟掉的垃圾還占著有用的空間沒有被及時(shí)丟掉。

解決辦法:

1)使用取消訂閱管理器朱监,compositeSubscription.讓訂閱管理器統(tǒng)一管理持有所有請(qǐng)求岸啡,統(tǒng)一取消。

2)使用Rxlifecycle第三方庫赌朋,完成發(fā)布事件與當(dāng)前組件進(jìn)行綁定凰狞,實(shí)現(xiàn)生命周期同步,組件生命周期結(jié)束后沛慢,自動(dòng)取消訂閱赡若。

3) 自己取消訂閱,調(diào)用unsubscribe()方法

5.timer和timertask(屬性動(dòng)畫)引起的內(nèi)存泄漏团甲,因?yàn)槲覀兺ǔ?huì)用來做一些計(jì)時(shí)操作或者循環(huán)操作,如果忘記銷毀變量的話身腻,那么timer或者timertask可能會(huì)一直持有著activity或者其他變量嘀趟,造成內(nèi)存泄漏她按。屬性動(dòng)畫和上述的問題是一樣的,所以在這里就集中地說了酌泰。

解決辦法:

1)在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用cancel()方法(比如在activity的ondestory方法里面調(diào)用cancel方法)

6.關(guān)于webview的內(nèi)存泄漏陵刹,是因?yàn)閣ebview加載網(wǎng)頁后長期占用內(nèi)存而不能釋放衰琐,也就是說webview持有著acitivity變量也糊,導(dǎo)致占用的內(nèi)存始終無法釋放,就算是調(diào)用了webview.ondestory()也不能解決問題碘耳。

解決方法:

1)當(dāng)然了显设,也有最終的解決方案框弛。在webview銷毀之前需要先從父容器中將webview移除辛辨。然后在調(diào)用webview的銷毀方法。

Android中檢查內(nèi)存泄漏的方法有很多種瑟枫,我們這里就介紹平時(shí)我們最常用的方法斗搞。

1.利用Android Studio自帶工具進(jìn)行內(nèi)存泄漏的檢測。首先我們先打開Android Studio的控制臺(tái)(logcat),然后找到monitors,打開慷妙。我們就可以看到下面這張圖這樣僻焚。

?????? 當(dāng)我們連接模擬器運(yùn)行項(xiàng)目的時(shí)候膝擂,我們就可以通過自帶工具看到我們的內(nèi)存使用情況,當(dāng)然還有其他的一些功能狞山,(cpu的消耗情況,網(wǎng)絡(luò)測速叉寂,Gpu的繪制情況)。我們都可以在這里看到屏鳍。如果發(fā)生內(nèi)存泄漏或者內(nèi)存溢出的話我們就可以發(fā)現(xiàn)勘纯,但是這種方法不全面,必須要我們關(guān)注它的內(nèi)存消耗狀況钓瞭。不夠方便驳遵,我們需要的是如果有內(nèi)存泄漏的話堤结,能夠第一時(shí)間的通知我們?nèi)ソ鉀Q媒惕,并且將發(fā)生內(nèi)存泄漏的位置告訴我們月弛。如果這樣的話帽衙,這種方式就不能滿足我們的需求了。這個(gè)時(shí)候我們就需要另外一種方法了章母。

2.利用Leakcanary來檢查我們項(xiàng)目中出現(xiàn)的內(nèi)存泄漏前弯。

leakcanary是square公司出的一個(gè)第三方檢查內(nèi)存泄漏的工具询枚,在這個(gè)工具出現(xiàn)之前狈醉,square公司的技術(shù)人員也被內(nèi)存泄漏的問題困擾了很久,當(dāng)時(shí)他們想要利用一種思路嘶炭,一種方法抱慌,徹底解決內(nèi)存泄漏的問題。但是后來失敗了眨猎。他們發(fā)現(xiàn)他們距離解決問題的方向更遙遠(yuǎn)了抑进。后來及時(shí)的調(diào)整思路,這才有了leakcanary睡陪。所以說并不是大公司的技術(shù)人員不會(huì)被這些問題困擾寺渗,人和人都是一樣的。區(qū)分的只是人與人的耐心程度兰迫。

下面來介紹一下這個(gè)工具具體是怎么使用的信殊。因?yàn)槭堑谌焦ぞ撸晕覀冃枰獙?dǎo)入兩個(gè)依賴汁果。一個(gè)是debug,一個(gè)是release

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'

releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'

通常來說涡拘,我們需要使用這個(gè)工具能夠檢測整個(gè)項(xiàng)目中的內(nèi)存泄漏問題,所以我還是建議抽取一個(gè)app類据德,在這個(gè)類中的oncreate方法中獲得檢測對(duì)象RefWatcher鳄乏。

refWatcher= LeakCanary.install(this);

然后通過application類傳遞出去。具體方法如下:

public static RefWatcher getRegwatcher(Context context){

MyApp myApp = (MyApp) context.getApplicationContext();

return myApp.refWatcher;

}

然后我們就可以使用了晋控,通過兩行代碼的調(diào)用汞窗,我們就可以實(shí)現(xiàn)實(shí)時(shí)的檢測我們項(xiàng)目中是否出現(xiàn)了內(nèi)存泄漏姓赤。如果我們想要在MainActivity中檢測內(nèi)存泄漏赡译,我們應(yīng)該具體怎么寫呢?具體代碼如下:

RefWatcher regwatcher = MyApp.getRegwatcher(this.getApplicationContext());

regwatcher.watch(this);

通過獲得到檢測對(duì)象RefWatcher不铆,將我們需要檢測的對(duì)象傳遞給它的watch()方法就可以了蝌焚。

當(dāng)然還有其他檢測方法,比如MAT等等誓斥,具體使用什么方法還是要看個(gè)人本身或者項(xiàng)目中的實(shí)際需求只洒,不可盲目使用。

到這里關(guān)于Android的內(nèi)存泄漏常見的原因和檢測方法就講述結(jié)束了劳坑,關(guān)于內(nèi)存泄漏其實(shí)還有很多我們還未了解到的知識(shí)毕谴,這些書本是不會(huì)交給我們的,我們需要實(shí)際的去體驗(yàn)距芬,在項(xiàng)目中碰到涝开,我們才能夠?qū)?nèi)存泄漏有更精進(jìn)的了解,當(dāng)然如果項(xiàng)目中沒有任何內(nèi)存泄漏框仔,那肯定是天大的好事舀武。以上我說的如果不對(duì)的地方,歡迎各位提出寶貴的意見离斩,一起交流银舱,共同進(jìn)步瘪匿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寻馏,隨后出現(xiàn)的幾起案子棋弥,更是在濱河造成了極大的恐慌,老刑警劉巖诚欠,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘁锯,死亡現(xiàn)場離奇詭異,居然都是意外死亡聂薪,警方通過查閱死者的電腦和手機(jī)家乘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來藏澳,“玉大人仁锯,你說我怎么就攤上這事∠栌疲” “怎么了业崖?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蓄愁。 經(jīng)常有香客問我双炕,道長,這世上最難降的妖魔是什么撮抓? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任妇斤,我火速辦了婚禮,結(jié)果婚禮上丹拯,老公的妹妹穿的比我還像新娘站超。我一直安慰自己,他們只是感情好乖酬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布死相。 她就那樣靜靜地躺著,像睡著了一般咬像。 火紅的嫁衣襯著肌膚如雪算撮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天县昂,我揣著相機(jī)與錄音肮柜,去河邊找鬼。 笑死七芭,一個(gè)胖子當(dāng)著我的面吹牛素挽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狸驳,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼预明,長吁一口氣:“原來是場噩夢啊……” “哼缩赛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撰糠,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤酥馍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后阅酪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旨袒,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年术辐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砚尽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辉词,死狀恐怖必孤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瑞躺,我是刑警寧澤敷搪,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站幢哨,受9級(jí)特大地震影響赡勘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捞镰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一闸与、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧曼振,春花似錦几迄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽木羹。三九已至甲雅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坑填,已是汗流浹背抛人。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脐瑰,地道東北人妖枚。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像苍在,于是被迫代替她去往敵國和親绝页。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荠商,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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