android 內(nèi)存泄漏

內(nèi)存泄漏

內(nèi)存管理???????????????????????????????????

內(nèi)存模型

??? Android原生開發(fā)以java為主。

在java中成箫,Java內(nèi)存模型展箱,往往是指Java程序在運(yùn)行時內(nèi)存的模型,而Java代碼是運(yùn)行在Java虛擬機(jī)之上的蹬昌,所以Java內(nèi)存模型混驰,也就是指Java虛擬機(jī)的運(yùn)行時內(nèi)存模型。

?java中內(nèi)存全權(quán)交給虛擬機(jī)去管理皂贩,那虛擬機(jī)的運(yùn)行時內(nèi)存是如何構(gòu)成的栖榨?


很多時候,我們提到內(nèi)存先紫,會說到堆和棧治泥,這是對內(nèi)存粗略的一種劃分,這種劃分的”堆”對應(yīng)內(nèi)存模型的Java堆遮精,”椌蛹校”是指虛擬機(jī)棧,但是實際上Java內(nèi)存模型比這復(fù)雜多了本冲。


?????? 在曾經(jīng)的日公司(sun 已被甲骨文2009年收購) 制定的java虛擬機(jī)規(guī)范中准脂,運(yùn)行時內(nèi)存模型,分為線程私有和共享數(shù)據(jù)區(qū)兩大類檬洞,其中線程私有的數(shù)據(jù)區(qū)包含程序計數(shù)器狸膏、虛擬機(jī)棧、本地方法區(qū)添怔,所有線程共享的數(shù)據(jù)區(qū)包含Java堆湾戳、方法區(qū),在方法區(qū)內(nèi)有一個常量池广料。


2.1 程序計數(shù)器PC

程序計數(shù)器PC是一塊較小的內(nèi)存空間砾脑,可以看作所執(zhí)行字節(jié)碼的行號指示器。字節(jié)碼解釋器就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令艾杏,比如循環(huán)韧衣、跳轉(zhuǎn)、異常處理等等這些基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。

在開發(fā)多線程應(yīng)用時畅铭,由于Java中的多線程是搶占式的調(diào)用氏淑,也就是任何一個確定的時刻,cpu都只會執(zhí)行一條線程硕噩,執(zhí)行哪條線程也是不確定的假残。所以為了線程切換后能夠回到正確的執(zhí)行位置,每個線程都需要一個獨(dú)立的程序計數(shù)器榴徐,各條線程之間計數(shù)器互不影響匀归,獨(dú)立存儲坑资,所以這塊區(qū)域是”線程私有”的內(nèi)存。

當(dāng)線程正在執(zhí)行一個Java方法時,PC計數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼的地址攒巍;當(dāng)線程正在執(zhí)行的一個Native方法時,PC計數(shù)器則為空(Undefined)。這一塊的內(nèi)存區(qū)域是唯一一個在java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError的區(qū)域跨蟹。

2.2 虛擬機(jī)棧

和程序計數(shù)器一樣橘沥,虛擬機(jī)棧也是線程私有的窗轩,它的生命周期與線程相同。虛擬機(jī)棧描述的是java方法執(zhí)行的內(nèi)存模型座咆。

每個方法(不包含native方法)執(zhí)行的同時都會創(chuàng)建一個棧幀 用于存儲局部變量表痢艺、操作數(shù)棧、動態(tài)鏈接介陶、方法出口等信息堤舒。

?????? 我們平時所說的棧內(nèi)存就是指的這一塊區(qū)域。

Java虛擬機(jī)規(guī)范規(guī)定該區(qū)域有兩種異常:

StackOverFlowError:當(dāng)線程請求棧深度超出虛擬機(jī)棧所允許的深度時拋出 (遞歸函數(shù))

OutOfMemoryError:當(dāng)Java虛擬機(jī)動態(tài)擴(kuò)展到無法申請足夠內(nèi)存時拋出(OOM)

2.3 本地方法棧

本地方法棧和虛擬機(jī)棧差不多斤蔓,前者是為虛擬機(jī)使用到的Native方法提供內(nèi)存空間植酥。有些虛擬機(jī)的實現(xiàn)直接把本地方法棧和虛擬機(jī)棧合二為一,比如主流的HotSpot虛擬機(jī)。

異常(Exception):Java虛擬機(jī)規(guī)范規(guī)定該區(qū)域可拋出StackOverFlowError和OutOfMemoryError友驮。

2.4 Java堆

Java堆漂羊,是Java虛擬機(jī)管理的最大的一塊內(nèi)存,也是GC的主戰(zhàn)場卸留,所以可以叫它gc堆(垃圾堆)走越,里面存放的是幾乎所有的對象實例和數(shù)組數(shù)據(jù)。

異常(Exception):Java虛擬機(jī)規(guī)范規(guī)定該區(qū)域可拋出OutOfMemoryError耻瑟。

2.5 方法區(qū)

方法區(qū)主要存放的是已被虛擬機(jī)加載的類信息旨指、常量、靜態(tài)變量喳整、編譯器編譯后的代碼等數(shù)據(jù)谆构。Java虛擬機(jī)規(guī)范對這一塊區(qū)域的限制非常寬松,不同的虛擬機(jī)實現(xiàn)也不同框都,相對而言垃圾回收在這個區(qū)域比較少的出現(xiàn)搬素。根據(jù)java虛擬機(jī)規(guī)范,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時魏保,會拋出oom異常熬尺。

2.6 運(yùn)行時常量池

運(yùn)行時常量池是方法區(qū)的一部分,用于存放編譯器生成的各種字面量和符號引用谓罗。運(yùn)行時常量池除了編譯期產(chǎn)生的Class文件的常量池粱哼,還可以在運(yùn)行期間,將新的常量加入常量池檩咱,比較String類的intern()方法揭措。

字面量:與Java語言層面的常量概念相近,包含文本字符串税手、聲明為final的常量值等蜂筹。

符號引用:編譯語言層面的概念,包括以下3類:

類和接口的全限定名

字段的名稱和描述符

方法的名稱和描述符

屬于方法區(qū)一部分芦倒,所以和方法區(qū)一樣艺挪,會oom。


局部變量的基本數(shù)據(jù)類型和引用存儲于棧中兵扬,引用的對象實體存儲于堆中麻裳。

——因為它們屬于方法中的變量,生命周期隨方法而結(jié)束器钟。

成員變量全部存儲與堆中(包括基本數(shù)據(jù)類型津坑,引用和引用的對象實體)

——因為它們屬于類,類對象終究是要被new出來使用的傲霸。


我們說的內(nèi)存泄露疆瑰,是針對眉反,也只針對堆內(nèi)存,他們存放的就是引用指向的對象實體穆役。


內(nèi)存的分配是由程序完成的寸五,而內(nèi)存的釋放是由垃圾收集器(Garbage Collection,GC)完成的耿币,java程序員不需要通過調(diào)用函數(shù)來釋放內(nèi)存梳杏,但gc只能回收無用并且不再被其它對象引用的那些對象所占用的空間。


堆中幾乎存放著Java世界中所有的對象實例淹接,垃圾收集器在對堆回收之前十性,第一件事情就是要確定這些對象哪些還“存活”著,哪些對象已經(jīng)“死去”(即不可能再被任何途徑使用的對象)


確定對象是否活著的方法有:

1塑悼、引用計數(shù)算法

?????? 1.1算法分析

引用計數(shù)是垃圾收集器中的早期策略劲适。在這種方法中,堆中每個對象實例都有一個引用計數(shù)拢肆。當(dāng)一個對象被創(chuàng)建時减响,且將該對象實例分配給一個變量,該變量計數(shù)設(shè)置為1郭怪。當(dāng)任何其它變量被賦值為這個對象的引用時,計數(shù)加1(a = b,則b引用的對象實例的計數(shù)器+1)刊橘,當(dāng)一個對象實例的某個引用超過了生命周期或者被設(shè)置為一個新值時鄙才,對象實例的引用計數(shù)器減1。任何引用計數(shù)器為0的對象實例可以被當(dāng)作垃圾收集促绵。當(dāng)一個對象實例被垃圾收集時攒庵,它引用的任何對象實例的引用計數(shù)器減1。

1.2優(yōu)缺點

優(yōu)點:

  引用計數(shù)收集器可以很快的執(zhí)行败晴,交織在程序運(yùn)行中浓冒。對程序需要不被長時間打斷的實時環(huán)境比較有利。

缺點:

  無法檢測出循環(huán)引用尖坤。如父對象有一個對子對象的引用稳懒,子對象反過來引用父對象。這樣慢味,他們的引用計數(shù)永遠(yuǎn)不可能為0.

1引用計數(shù)算法無法解決循環(huán)引用問題场梆,例如:

public class Main {

????????public static void main(String[] args) {

????????????????MyObject object1 = new MyObject();

????????????????MyObject object2 = new MyObject();

????????????????object1.object = object2;

????????????????object2.object = object1;

????????????????object1 = null;

? ? ? ? ? ? ? ? object2 = null;

????}

}

最后面兩句將object1和object2賦值為null,也就是說object1和object2指向的對象已經(jīng)不可能再被訪問纯路,但是由于它們互相引用對方或油,導(dǎo)致它們的引用計數(shù)器都不為0,那么垃圾收集器就永遠(yuǎn)不會回收它們驰唬。



2顶岸、可達(dá)性分析算法(主流方法)


可達(dá)性分析算法中腔彰,通過一系列的gc root為起始點,從一個GC ROOT開始辖佣,尋找對應(yīng)的引用節(jié)點萍桌,找到這個節(jié)點以后,繼續(xù)尋找這個節(jié)點的引用節(jié)點凌简,當(dāng)所有的引用節(jié)點尋找完畢之后上炎,剩余的節(jié)點則被認(rèn)為是沒有被引用到的節(jié)點,即無用的節(jié)點雏搂。

java中可作為GC Root的對象有

  1.虛擬機(jī)棧(本地變量表)中正在運(yùn)行使用的引用

  2.方法區(qū)中靜態(tài)屬性引用的對象

  3. 方法區(qū)中常量引用的對象

4.本地方法棧JNI中引用的對象(Native對象)

上圖中objD與objE到GC ROOT不可達(dá)藕施,所以可以被回收。而其他的對gc root可達(dá)凸郑。

在代碼看來裳食,類似于:(GC/Main.java)


但是即使在可達(dá)性分析算法中不可達(dá)的對象,也并非一定要死芙沥。當(dāng)gc第一次掃過這些對象的時候诲祸,他們處于“死緩”的階段。要真正執(zhí)行死刑,至少需要經(jīng)過兩次標(biāo)記過程岩遗。如果對象經(jīng)過可達(dá)性分析之后發(fā)現(xiàn)沒有與GC Roots相關(guān)聯(lián)的引用鏈箕昭,那他會被第一次標(biāo)記,并經(jīng)歷一次篩選着憨。這個對象的finalize方法會被執(zhí)行。如果對象沒有覆蓋finalize或者已經(jīng)被執(zhí)行過了务嫡。虛擬機(jī)也不會去執(zhí)行finalize方法甲抖。Finalize是對象逃獄的最后一次機(jī)會。

Reference項目的FinalizeEscapeGC


在例子中心铃,對象第一次被執(zhí)行了finalize方法准谚,但是把自己上交給國家逃了一死,但是在給國家執(zhí)行任務(wù)的時候去扣,不幸犧牲了柱衔。所以沒辦法再自救了。

這個對象的finalize方法執(zhí)行了一次(自救而不是被救厅篓,this賦值秀存,所以給的還是自己)

內(nèi)存泄漏

在說到內(nèi)存的問題,我們都會提到一個關(guān)鍵詞:引用羽氮。

??? 通俗的講或链,通過A能調(diào)用并訪問到B,那就說明A持有B的引用档押,或A就是B的引用澳盐。

?????? 比如 Person p1 = new

Person();通過P1能操作Person對象祈纯,因此P1是Person的引用;p1是類O中的一個成員變量叼耙,因此我們可以使用o.p1的方式來訪問Person類對象的成員腕窥,因此o持有一個Person對象的引用。


?????? GC過程與對象的引用類型是密切相關(guān)的筛婉,

Java對引用的分類Strong

reference(強(qiáng)), SoftReference(軟), WeakReference(弱), PhatomReference(虛)

強(qiáng)引用就是在程序代碼中普遍存在的簇爆,比如”O(jiān)bject obj = new Object()”這種引用,只要強(qiáng)引用還在爽撒,垃圾收集器就不會回收被引用的對象入蛆。

軟引用用來定義一些還有用但并非必須的對象。對于軟引用關(guān)聯(lián)著的對象硕勿,在系統(tǒng)將要內(nèi)存溢出之前哨毁,會將這些對象列入回收范圍進(jìn)行第二次回收,如果回收后還是內(nèi)存不足源武,才會拋出內(nèi)存溢出扼褪。

弱引用也是用來描述非必須對象。但他的強(qiáng)度比軟引用更弱一些粱栖。被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前话浇。當(dāng)垃圾收集器回收時,無論內(nèi)存是否足夠查排,都會回收掉被弱引用關(guān)聯(lián)的對象凳枝。

虛引用也稱為幽靈引用或者幻影引用,是最弱的引用關(guān)系跋核。一個對象的虛引用根本不影響其生存時間,也不能通過虛引用獲得一個對象實例叛买。虛引用的唯一作用就是這個對象被GC時可以收到一條系統(tǒng)通知砂代。


在Android應(yīng)用的開發(fā)中,為了防止內(nèi)存溢出率挣,在處理一些占用內(nèi)存大而且生命周期較長的對象時候刻伊,可以盡量應(yīng)用軟引用和弱引用技術(shù)。


對于軟引用和弱引用的選擇椒功,

如果只是想避免OutOfMemory異常的發(fā)生捶箱,則可以使用軟引用。如果對于應(yīng)用的性能更在意动漾,想盡快回收一些占用內(nèi)存比較大的對象丁屎,則可以使用弱引用。另外可以根據(jù)對象是否經(jīng)常使用來判斷選擇軟引用還是弱引用旱眯。如果該對象可能會經(jīng)常使用的晨川,就盡量用軟引用证九。如果該對象不被使用的可能性更大些,就可以用弱引用共虑。


內(nèi)存泄漏就是

堆內(nèi)存中的生命周期的象持有短生命周期象的引用愧怜,盡管短生命周期象已經(jīng)不再需要,但是因為長生命周期象持有它的引用而導(dǎo)致不能被回收妈拌,就是Java中內(nèi)存泄露的根本原因拥坛。

?總結(jié)一句就是不需要了該回收因引用問題導(dǎo)致不能回收。


內(nèi)存泄漏會導(dǎo)致可用內(nèi)存慢慢變少尘分,讓程序慢慢變卡猜惋。最終還會導(dǎo)致臭名昭著的oom 內(nèi)存溢出。

我們的android程序應(yīng)該怎么排查內(nèi)存泄漏問題呢音诫?

在android中我們執(zhí)行一段代碼惨奕,比如進(jìn)入了一個新的頁面(Activity),這時候我們的內(nèi)存使用肯定比在前一個頁面大竭钝,而在界面finish返回后梨撞,如果內(nèi)存沒有回落,那么很有可能就是出現(xiàn)了內(nèi)存泄漏香罐。


從內(nèi)存監(jiān)控工具中觀察內(nèi)存曲線卧波,是否存在不斷上升的趨勢且不會在程序返回時明顯回落。這種方式可以發(fā)現(xiàn)最基本庇茫,也是最明顯的內(nèi)存泄露問題港粱,對用戶價值最大,操作難度小旦签,性價比極高查坪。

因為他能發(fā)現(xiàn)很明顯很嚴(yán)重的內(nèi)存泄漏問題


我們可以通過AS的Memory Profile或者DDMS中的heap觀察內(nèi)存使用情況。




Android Profile

?? https://developer.android.com/studio/preview/features/android-profiler.html

?

????? The newAndroid

Profilerwindow in Android Studio 3.0 replaces theAndroid Monitortools.

?

?????? 在Android Studio我們可以點擊

運(yùn)行app宁炫。

然后我們能看到


我們點擊memory后


① 強(qiáng)制執(zhí)行垃圾收集事件的按鈕偿曙。

② 捕獲堆轉(zhuǎn)儲的按鈕。

③ 記錄內(nèi)存分配的按鈕羔巢。

④ 放大時間線的按鈕望忆。

⑤ 跳轉(zhuǎn)到實時內(nèi)存數(shù)據(jù)的按鈕。

⑥ 事件時間線顯示活動狀態(tài)竿秆、用戶輸入事件和屏幕旋轉(zhuǎn)事件启摄。

⑦ 內(nèi)存使用時間表,其中包括以下內(nèi)容:

每個內(nèi)存類別使用多少內(nèi)存的堆棧圖幽钢,如左邊的y軸和頂部的顏色鍵所示歉备。

虛線表示已分配對象的數(shù)量,如右側(cè)y軸所示搅吁。

每個垃圾收集事件的圖標(biāo)威创。


與之前的Android監(jiān)控工具相比落午,新的內(nèi)存分析器記錄了更多內(nèi)存使用情況,所以看起來你的內(nèi)存使用量會更高肚豺。內(nèi)存分析器監(jiān)視一些額外的類別溃斋,這些類別增加了總數(shù)。



在比較茫然的情況下吸申,不知道哪兒出現(xiàn)了內(nèi)存泄漏梗劫,我們可以進(jìn)行一刀切,來個大致的排查截碴。


我們進(jìn)入了一大堆頁面并最終返回到主頁梳侨。

然后gc ,再dump下內(nèi)存查看日丹。AS可以點擊

然后等待一段時間會出現(xiàn):


但是說實話這個頁面想要分析出什么很難走哺。一般不會使用這個頁面來進(jìn)行分析,最多打開看一眼剛剛我們進(jìn)入的Activity是否因為我們退出而回收哲虾。


先按照包名來分組丙躏,


Alloc Cout : 對象數(shù)

Shallow Size : 對象占用內(nèi)存大小

Retained Set : 對象引用組占用內(nèi)存大小(包含了這個對象引用的其他對象)


當(dāng)然一次dump可能并不能發(fā)現(xiàn)內(nèi)存泄漏,可能每次我們dump的結(jié)果都不同束凑,那么就需要多試幾次晒旅,然后結(jié)合代碼來排查。



這里還不能確定發(fā)生了內(nèi)存泄漏汪诉。

我們這時候可以借助一些更專業(yè)的工具來進(jìn)行內(nèi)存的分析废恋。


我們先把

這個內(nèi)存快照保存為hprof文件。

AS自動分析

其實現(xiàn)在的AS扒寄,可以說是非常強(qiáng)大了鱼鼓。我們把剛剛保存的hprof文件拖入到AS中。


這個自動分析任務(wù)包含了兩個內(nèi)容该编,一個是檢測Activity的泄漏蚓哩,一個是檢測重復(fù)字符串。

點擊運(yùn)行分析:


這里出現(xiàn)了MainActivity的泄漏上渴。并且觀察到這個MainActivity可能不止一個對象存在,可能是我們上次退出程序的時候發(fā)生了泄漏喜颁,導(dǎo)致它不能回收稠氮。而在此打開app,系統(tǒng)會創(chuàng)建新的MainActivity半开。

但是在AS上發(fā)現(xiàn)為何沒被回收需要運(yùn)氣隔披,更方便的工具是Mat。



Memory Analyzer Tool基于eclipse

可以直接下載:

http://www.eclipse.org/mat/downloads.php

也可以在eclispe上安裝mat插件:


點擊eclipse? ? marketplace...搜索memory寂拆。


在使用mat之前我們需要把快照文件轉(zhuǎn)換一下,

轉(zhuǎn)換工具在sdk/platform-tools/hprof-conv

-z:排除不是app的內(nèi)存奢米,比如Zygote

hprof-conv -z src dst



然后在Mat中打開它:


打開我們的快照文件抓韩,


之后我們能看到


我們點擊


以直方圖的方式來顯示當(dāng)前內(nèi)存使用情況可能更加適合較為復(fù)雜的內(nèi)存泄漏分析,它默認(rèn)直接顯示當(dāng)前內(nèi)存中各種類型對象的數(shù)量及這些對象的shallow heap和retained heap鬓长。結(jié)合MAT提供的不同顯示方式谒拴,往往能夠直接定位問題

shallow heap:指的是某一個對象所占內(nèi)存大小。

retained heap:指的是一個對象與所包含對象所占內(nèi)存的總大小涉波。


out查看這個對象持有的外部對象引用英上。

incoming查看這個對象被哪些外部對象引用。


我們現(xiàn)在希望查看為什么這個對象還存在啤覆,那么


排除軟弱虛引用苍日。


關(guān)于這個問題是android系統(tǒng)的一個bug。

原因是Activity的DecorView請求了InputMethodManager窗声,而InputMethodManager一直持有DecorView的引用相恃,導(dǎo)致無法釋放Activity。

解決辦法是:

這個問題是一個第三方庫中持有引用笨觅,導(dǎo)致的無法釋放拦耐。

這個問題可能是這個第三方庫本身的問題,也可能是我們使用不當(dāng)引起的屋摇。為了確定這個問題揩魂。我們進(jìn)入被混淆了的g.e這個類,

注意這里是g.e中的a成員是一個HashMap,


結(jié)合代碼


這里面保存的都是一些Observer對象炮温。這里就需要結(jié)合對代碼的熟悉度火脉,加上一些猜測來尋找問題。

在Activity中搜索Observer,可以找到很多個new Observer柒啤。

但是我們注意倦挂,調(diào)用的地方第二個參數(shù)是true,表示注冊;如果傳false表示注銷


而這里只有注冊担巩,沒有注銷方援。


我們在onDestory中加入注銷。


修改完成再次運(yùn)行,然后退出app一次再打開:

結(jié)果:

還有兩個Activity涛癌。

第一個問題仍然是observer,但是是在MessageFragment犯戏。

第二個問題也還是InputMethodmanager,但是泄漏點變了。

修改:


內(nèi)存泄漏解決完成拳话!

??? 這個app還有很多內(nèi)存泄漏的地方先匪。大家可以自己嘗試去尋找并解決。比如MyInfoActivity弃衍。呀非。。。大家自己去解決啊岸裙,有什么不懂得再問我猖败。如果問的人多,下節(jié)課先解決掉這個泄漏降允。


除了檢查單個hprof文件之外恩闻,還能夠使用多個hprof進(jìn)行對比。

比如我們在進(jìn)入一個新頁面之前先dump下來內(nèi)存拟糕。然后再進(jìn)入這個頁面之后退出判呕,再dump一份內(nèi)存。通過對比就能夠知道送滞,進(jìn)入這個頁面之后增加了多少內(nèi)存/對象等信息:


之后在mat中打開這個兩個文件并都切換到直方圖查看侠草。

再然后把兩個直方均加入對比,點擊


或者


執(zhí)行對比:



再把視圖切換到difference from base table(與第一個的不同)



然后能看到


第二行就是指第二個文件相比第一個文件多出來了幾個對象。

如果存在增加了不合理的對象犁嗅,同樣可以查看其GC root边涕。???

=====================================================================

對Android內(nèi)存泄露 我們還可以使用著名的LeakCanary

(Square出品,Square可謂Android開源界中的業(yè)界良心褂微,開源的項目包括okhttp, retrofit功蜓,otto, picasso, Android開發(fā)大神Jake Wharton曾今就是Square)來進(jìn)行檢測

https://github.com/square/leakcanary

這個庫也有一些bug,但總體來說還是能起到一定的輔助作用宠蚂。


總結(jié):

內(nèi)存泄漏常見原因:

1.集合類

集合類如果僅僅有添加元素的方法式撼,而沒有相應(yīng)的刪除機(jī)制,導(dǎo)致內(nèi)存被占用求厕。如果這個集合類是全局性的變量 (比如類中的靜態(tài)屬性著隆,全局性的 map 等即有靜態(tài)引用或 final 一直指向它),那么沒有相應(yīng)的刪除機(jī)制呀癣,很可能導(dǎo)致集合所占用的內(nèi)存只增不減美浦。

?????? 一開始的微信工程中使用的ButterKnife中的linkeahashmap就存在這個問題。

2项栏、靜態(tài)成員

?????? Static成員作為gc root浦辨,如果一個對象被static聲明,這個對象會一直存活直到程序進(jìn)程停止沼沈。

2.單例模式

不正確使用單例模式是引起內(nèi)存泄露的一個常見問題流酬,單例對象在被初始化后將在 JVM 的整個生命周期中存在(以靜態(tài)變量的方式),如果單例對象持有外部對象的引用列另,那么這個外部對象將不能被 JVM 正晨党常回收,導(dǎo)致內(nèi)存泄露访递。


這里如果傳遞Activity作為Context來獲得單例對象,那么單例持有Activity的引用同辣,導(dǎo)致Activity不能被釋放拷姿。

不要直接對 Activity 進(jìn)行直接引用作為成員變量惭载,如果允許可以使用Application。如果不得不需要Activity作為Context响巢,可以使用弱引用WeakReference描滔,相同的,對于Service 等其他有自己聲明周期的對象來說踪古,直接引用都需要謹(jǐn)慎考慮是否會存在內(nèi)存泄露的可能含长。


3.未關(guān)閉/釋放資源

BraodcastReceiver,ContentObserver伏穆,F(xiàn)ileObserver拘泞,Cursor,Callback等在 Activity

onDestroy 或者某類生命周期結(jié)束之后一定要 unregister 或者 close 掉枕扫,否則這個 Activity 類會被 system 強(qiáng)引用陪腌,不會被內(nèi)存回收。

?????? 我們經(jīng)常會寫出下面的代碼


當(dāng)然這樣寫代碼沒問題烟瞧,但是如果我們在close之前還有一些可能拋出異常的代碼


那么現(xiàn)在這段代碼存在隱患的诗鸭。因為如果運(yùn)行到fos2時候拋出了異常,那么fos也沒辦法close参滴。

所以正確的方式應(yīng)該是


因為如果write發(fā)生異常那么這個fos會因為沒有close造成內(nèi)存泄漏强岸。

4.

Handler

只要 Handler 發(fā)送的 Message 尚未被處理,則該 Message 及發(fā)送它的 Handler 對象將被線程 MessageQueue 一直持有砾赔。特別是handler執(zhí)行延遲任務(wù)蝌箍。所以,Handler 的使用要尤為小心过蹂,否則將很容易導(dǎo)致內(nèi)存泄露的發(fā)生十绑。


這種創(chuàng)建Handler的方式會造成內(nèi)存泄漏,由于mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實例酷勺,所以它持有外部類Activity的引用本橙,我們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那么當(dāng)這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息脆诉,而消息隊列中的Message持有mHandler實例的引用甚亭,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收击胜,引發(fā)內(nèi)存泄漏亏狰,所以另外一種做法為:


創(chuàng)建一個靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象使用弱引用偶摔,這樣在回收時也可以回收Handler持有的對象暇唾,這樣雖然避免了Activity泄漏,不過Looper線程的消息隊列中還是可能會有待處理的消息,所以我們在Activity的Destroy時或者Stop時應(yīng)該移除消息隊列中的消息策州,


使用mHandler.removeCallbacksAndMessages(null);是移除消息隊列中所有消息和所有的Runnable瘸味。當(dāng)然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();來移除指定的Runnable和Message。


5.

Thread 內(nèi)存泄露

和handler一樣够挂,線程也是造成內(nèi)存泄露的一個重要的源頭旁仿。線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控。比如線程是 Activity 的內(nèi)部類孽糖,則線程對象中保存了 Activity 的一個引用枯冈,當(dāng)線程的 run 函數(shù)耗時較長沒有結(jié)束時,線程對象是不會被銷毀的办悟,因此它所引用的老的 Activity 也不會被銷毀尘奏,因此就出現(xiàn)了內(nèi)存泄露的問題。

Thread和Handler都可以劃分到為非靜態(tài)包括匿名內(nèi)部類的內(nèi)存泄漏誉尖。


6罪既、系統(tǒng)bug,比如InputMethodManager

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末铡恕,一起剝皮案震驚了整個濱河市琢感,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌探熔,老刑警劉巖驹针,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诀艰,居然都是意外死亡柬甥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門其垄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苛蒲,“玉大人,你說我怎么就攤上這事绿满”弁猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵喇颁,是天一觀的道長漏健。 經(jīng)常有香客問我,道長橘霎,這世上最難降的妖魔是什么蔫浆? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮姐叁,結(jié)果婚禮上瓦盛,老公的妹妹穿的比我還像新娘洗显。我一直安慰自己,他們只是感情好谭溉,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布墙懂。 她就那樣靜靜地躺著,像睡著了一般扮念。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碧库,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天柜与,我揣著相機(jī)與錄音,去河邊找鬼嵌灰。 笑死弄匕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沽瞭。 我是一名探鬼主播迁匠,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼驹溃!你這毒婦竟也來了城丧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤豌鹤,失蹤者是張志新(化名)和其女友劉穎亡哄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體布疙,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蚊惯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灵临。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片截型。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖儒溉,靈堂內(nèi)的尸體忽然破棺而出宦焦,到底是詐尸還是另有隱情,我是刑警寧澤睁搭,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布赶诊,位于F島的核電站,受9級特大地震影響园骆,放射性物質(zhì)發(fā)生泄漏舔痪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一锌唾、第九天 我趴在偏房一處隱蔽的房頂上張望锄码。 院中可真熱鬧夺英,春花似錦、人聲如沸滋捶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽重窟。三九已至载萌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巡扇,已是汗流浹背扭仁。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留厅翔,地道東北人乖坠。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像刀闷,于是被迫代替她去往敵國和親熊泵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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

  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題甸昏。內(nèi)存泄漏...
    _痞子閱讀 1,625評論 0 8
  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題顽分。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講筒扒,...
    宇宙只有巴掌大閱讀 2,360評論 0 12
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題怯邪。內(nèi)存泄漏...
    apkcore閱讀 1,217評論 2 7
  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了花墩,簡單粗俗的講悬秉,...
    DreamFish閱讀 790評論 0 5
  • 劉伯溫經(jīng)典之作,曠世奇文冰蘑,值得一讀再讀和泌! 劉伯溫是明初詩文三大家之一,曾輔佐朱元璋平天下祠肥,以神機(jī)妙算武氓、運(yùn)籌帷幄著稱...
    聞方培訓(xùn)師閱讀 626評論 0 1