內(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