閑談Android中的內(nèi)存泄漏

Part 1

在長(zhǎng)久以來的 Android 開發(fā)過程中南用,內(nèi)存泄漏一直是一個(gè)比較頭疼的問題熙卡。內(nèi)存泄漏會(huì)導(dǎo)致應(yīng)用卡頓洲脂,用戶體驗(yàn)不佳斤儿,甚至?xí)斐蓱?yīng)用崩潰的嚴(yán)重后果。所以如何科學(xué)地進(jìn)行內(nèi)存管理一直是大家探討的話題恐锦,從一開始主動(dòng)使用 MAT 分析 hprof 文件往果,到后來 LeakCanary “被動(dòng)”的接收內(nèi)存泄漏消息。應(yīng)用中發(fā)現(xiàn)內(nèi)存泄漏的手段越來越多了一铅,操作也越來越便捷陕贮,但內(nèi)存泄漏的問題還是不能輕易忽視的,提高應(yīng)用的體驗(yàn)和質(zhì)量也是迫在眉睫潘飘。

那今天肮之,就從最基本的開始聊聊內(nèi)存泄漏。

Part 2

內(nèi)存泄漏簡(jiǎn)單粗俗的講卜录,就是該被釋放的對(duì)象沒有釋放戈擒,一直被某個(gè)或某些實(shí)例所持有卻不再被使用導(dǎo)致 GC 不能回收。我們所說的內(nèi)存泄露是針對(duì)于堆內(nèi)存而言艰毒,堆內(nèi)存中存放的就是引用指向的對(duì)象實(shí)體筐高。

在這里先科普下內(nèi)存分配的三種策略。(以下這段講解來自于 《內(nèi)存泄露從入門到精通三部曲之基礎(chǔ)知識(shí)篇》

  • 靜態(tài)的现喳,使用的內(nèi)存空間是靜態(tài)存儲(chǔ)區(qū)
  • 棧式的凯傲,使用的內(nèi)存空間是棧區(qū)
  • 堆式的,使用的內(nèi)存空間是堆區(qū)

靜態(tài)存儲(chǔ)區(qū)(方法區(qū)):內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好嗦篱,這塊內(nèi)存在程序整個(gè)運(yùn)行期間都存在冰单。它主要存放靜態(tài)數(shù)據(jù)、全局static數(shù)據(jù)和常量灸促。

棧區(qū):在執(zhí)行函數(shù)時(shí)诫欠,函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建涵卵,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中荒叼,效率很高轿偎,但是分配的內(nèi)存容量有限。

堆區(qū):亦稱動(dòng)態(tài)內(nèi)存分配被廓。程序在運(yùn)行的時(shí)候用malloc或new申請(qǐng)任意大小的內(nèi)存坏晦,程序員自己負(fù)責(zé)在適當(dāng)?shù)臅r(shí)候用free或delete釋放內(nèi)存(Java則依賴?yán)厥掌鳎?dòng)態(tài)內(nèi)存的生存期可以由我們決定嫁乘,如果我們不釋放內(nèi)存昆婿,程序?qū)⒃谧詈蟛裴尫诺魟?dòng)態(tài)內(nèi)存。 但是蜓斧,良好的編程習(xí)慣是:如果某動(dòng)態(tài)內(nèi)存不再使用仓蛆,需要將其釋放掉。
接下來我們集中說下堆和棧的區(qū)別:

在函數(shù)中(說明是局部變量)定義的一些基本類型的變量和對(duì)象的引用變量都是在函數(shù)的棧內(nèi)存中分配挎春。當(dāng)在一段代碼塊中定義一個(gè)變量時(shí)看疙,java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)超過變量的作用域后直奋,java會(huì)自動(dòng)釋放掉為該變量分配的內(nèi)存空間能庆,該內(nèi)存空間可以立刻被另作他用。

堆內(nèi)存用于存放所有由new創(chuàng)建的對(duì)象(內(nèi)容包括該對(duì)象其中的所有成員變量)和數(shù)組帮碰。在堆中分配的內(nèi)存相味,由java虛擬機(jī)自動(dòng)垃圾回收器來管理拾积。在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后殉挽,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址拓巧,在棧中的這個(gè)特殊的變量就變成了數(shù)組或者對(duì)象的引用變量斯碌,以后就可以在程序中使用棧內(nèi)存中的引用變量來訪問堆中的數(shù)組或者對(duì)象,引用變量相當(dāng)于為數(shù)組或者對(duì)象起的一個(gè)別名肛度,或者代號(hào)傻唾。

堆是不連續(xù)的內(nèi)存區(qū)域(因?yàn)橄到y(tǒng)是用鏈表來存儲(chǔ)空閑內(nèi)存地址,自然不是連續(xù)的)承耿,堆大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存(32bit系統(tǒng)理論上是4G)冠骄,所以堆的空間比較靈活,比較大加袋。棧是一塊連續(xù)的內(nèi)存區(qū)域凛辣,大小是操作系統(tǒng)預(yù)定好的,windows下棧大小是2M(也有是1M职烧,在編譯時(shí)確定扁誓,VC中可設(shè)置)防泵。

對(duì)于堆,頻繁的new/delete會(huì)造成大量?jī)?nèi)存碎片蝗敢,使程序效率降低捷泞。對(duì)于棧,它是先進(jìn)后出的隊(duì)列寿谴,進(jìn)出一一對(duì)應(yīng)锁右,不產(chǎn)生碎片,運(yùn)行效率穩(wěn)定高讶泰。

說了這么多了骡湖,我們來看一個(gè)例子吧:

class Student {
    private int age = 10;
    private School school = new School();
    
    public void doHomework() {
        Book book = new Book();
        int pageNo = 15;
    }
}

Student s = new Student();

s 自己存放在棧中,而 s 指向的對(duì)象實(shí)體存放在堆中峻厚;

其中 s 這個(gè)對(duì)象實(shí)體中的全局變量 age 和 school 都是存放在堆中(包括基本數(shù)據(jù)類型响蕴、引用和引用的對(duì)象實(shí)體)

doHomework 中的引用變量 book 和局部變量 pageNo 是存放在棧中的,而引用變量 book 指向的對(duì)象是存放在堆中的惠桃。

結(jié)論:(以下結(jié)論來自于《Android 內(nèi)存泄漏探討》

局部變量的基本數(shù)據(jù)類型和引用存儲(chǔ)于棧中浦夷,引用的對(duì)象實(shí)體存儲(chǔ)于堆中」纪酰—— 因?yàn)樗鼈儗儆诜椒ㄖ械淖兞颗芷陔S方法而結(jié)束。
成員變量全部存儲(chǔ)與堆中(包括基本數(shù)據(jù)類型呐馆,引用和引用的對(duì)象實(shí)體)—— 因?yàn)樗鼈儗儆陬惙实蓿悓?duì)象終究是要被new出來使用的。

Part 3

那么有沒有想過汹来,內(nèi)存為什么會(huì)泄露续膳?

Java的內(nèi)存垃圾回收機(jī)制是從程序的主要運(yùn)行對(duì)象(如靜態(tài)對(duì)象/寄存器/棧上指向的堆內(nèi)存對(duì)象等)開始檢查引用鏈,當(dāng)遍歷一遍后得到上述這些無法回收的對(duì)象和他們所引用的對(duì)象鏈收班,組成無法回收的對(duì)象集合坟岔,而其他孤立對(duì)象(集)就作為垃圾回收。GC為了能夠正確釋放對(duì)象摔桦,必須監(jiān)控每一個(gè)對(duì)象的運(yùn)行狀態(tài)社付,包括對(duì)象的申請(qǐng)、引用邻耕、被引用鸥咖、賦值等,GC都需要進(jìn)行監(jiān)控兄世。監(jiān)視對(duì)象狀態(tài)是為了更加準(zhǔn)確地啼辣、及時(shí)地釋放對(duì)象,而釋放對(duì)象的根本原則就是該對(duì)象不再被引用碘饼。

在Java中熙兔,這些無用的對(duì)象都由GC負(fù)責(zé)回收悲伶,因此程序員不需要考慮這部分的內(nèi)存泄露。雖然住涉,我們有幾個(gè)函數(shù)可以訪問GC麸锉,例如運(yùn)行GC的函數(shù)System.gc(),但是根據(jù)Java語(yǔ)言規(guī)范定義舆声,該函數(shù)不保證JVM的垃圾收集器一定會(huì)執(zhí)行花沉。因?yàn)椴煌腏VM實(shí)現(xiàn)者可能使用不同的算法管理GC。通常GC的線程的優(yōu)先級(jí)別較低媳握。JVM調(diào)用GC的策略也有很多種碱屁,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開始工作蛾找,也有定時(shí)執(zhí)行的娩脾,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC打毛。但通常來說柿赊,我們不需要關(guān)心這些。

GC過程與對(duì)象的引用類型是嚴(yán)重相關(guān)的幻枉,我們來看看Java對(duì)引用的分類Strong reference, SoftReference, WeakReference, PhatomReference

20190629134830.png

在Android應(yīng)用的開發(fā)中碰声,為了防止內(nèi)存溢出,在處理一些占用內(nèi)存大而且聲明周期較長(zhǎng)的對(duì)象時(shí)候熬甫,可以盡量應(yīng)用軟引用和弱引用技術(shù)胰挑。

如果只是想避免OutOfMemory異常的發(fā)生,則可以使用軟引用椿肩。如果對(duì)于應(yīng)用的性能更在意瞻颂,想盡快回收一些占用內(nèi)存比較大的對(duì)象,則可以使用弱引用覆旱。

另外可以根據(jù)對(duì)象是否經(jīng)常使用來判斷選擇軟引用還是弱引用蘸朋。如果該對(duì)象可能會(huì)經(jīng)常使用的,就盡量用軟引用扣唱。如果該對(duì)象不被使用的可能性更大些,就可以用弱引用团南。

結(jié)論
堆內(nèi)存中的長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的強(qiáng)/軟引用噪沙,盡管短生命周期對(duì)象已經(jīng)不再需要,但是因?yàn)殚L(zhǎng)生命周期對(duì)象持有它的引用而導(dǎo)致不能被回收吐根,這就是Java中內(nèi)存泄露的根本原因正歼。

Part 4

Android中常見的內(nèi)存泄漏問題:

  • 單例造成的內(nèi)存泄露
  • InnerClass匿名內(nèi)部類
  • Activity Context 的不正確使用
  • Handler引起的內(nèi)存泄漏
  • 注冊(cè)監(jiān)聽器的泄漏
  • Cursor,Stream沒有close拷橘,View沒有recyle
  • 集合中對(duì)象沒清理造成的內(nèi)存泄漏
  • WebView造成的泄露
  • 構(gòu)造Adapter時(shí)局义,沒有使用緩存的ConvertView

具體可以參考 Android內(nèi)存泄漏分析心得

Part 5

Android 中檢測(cè)內(nèi)存泄漏的工具

  • MAT
  • Android Profiler
  • LeakCanary

Part 6

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喜爷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子萄唇,更是在濱河造成了極大的恐慌檩帐,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件另萤,死亡現(xiàn)場(chǎng)離奇詭異湃密,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)四敞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門泛源,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忿危,你說我怎么就攤上這事达箍。” “怎么了铺厨?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵幻梯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我努释,道長(zhǎng)碘梢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任伐蒂,我火速辦了婚禮煞躬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逸邦。我一直安慰自己恩沛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布缕减。 她就那樣靜靜地躺著雷客,像睡著了一般。 火紅的嫁衣襯著肌膚如雪桥狡。 梳的紋絲不亂的頭發(fā)上搅裙,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音裹芝,去河邊找鬼部逮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嫂易,可吹牛的內(nèi)容都是我干的兄朋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼怜械,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼颅和!你這毒婦竟也來了傅事?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤峡扩,失蹤者是張志新(化名)和其女友劉穎蹭越,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體有额,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡般又,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巍佑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茴迁。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖萤衰,靈堂內(nèi)的尸體忽然破棺而出堕义,到底是詐尸還是另有隱情,我是刑警寧澤脆栋,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布倦卖,位于F島的核電站,受9級(jí)特大地震影響椿争,放射性物質(zhì)發(fā)生泄漏怕膛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一秦踪、第九天 我趴在偏房一處隱蔽的房頂上張望褐捻。 院中可真熱鬧,春花似錦椅邓、人聲如沸柠逞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)板壮。三九已至,卻和暖如春合住,著一層夾襖步出監(jiān)牢的瞬間绰精,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工聊疲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茬底,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓获洲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親殿如。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贡珊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 被文同時(shí)發(fā)布在CSDN上最爬,歡迎查看。 APP內(nèi)存的使用门岔,是評(píng)價(jià)一款應(yīng)用性能高低的一個(gè)重要指標(biāo)爱致。雖然現(xiàn)在智能手機(jī)的內(nèi)...
    大圣代閱讀 4,807評(píng)論 2 54
  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了寒随,簡(jiǎn)單粗俗的講糠悯,...
    宇宙只有巴掌大閱讀 2,361評(píng)論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    _痞子閱讀 1,626評(píng)論 0 8
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題妻往。內(nèi)存泄漏...
    apkcore閱讀 1,219評(píng)論 2 7
  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題互艾。內(nèi)存泄漏大家都不陌生了,簡(jiǎn)單粗俗的講讯泣,...
    DreamFish閱讀 791評(píng)論 0 5