android 內(nèi)存泄漏

內(nèi)存泄漏

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

內(nèi)存模型

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

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

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


很多時(shí)候,我們提到內(nèi)存,會(huì)說到堆和棧,這是對(duì)內(nèi)存粗略的一種劃分,這種劃分的”堆”對(duì)應(yīng)內(nèi)存模型的Java堆,”?!笔侵柑摂M機(jī)棧,但是實(shí)際上Java內(nèi)存模型比這復(fù)雜多了。


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


2.1 程序計(jì)數(shù)器PC

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

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

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

2.2 虛擬機(jī)棧

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

每個(gè)方法(不包含native方法)執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀 用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。

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

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

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

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

2.3 本地方法棧

本地方法棧和虛擬機(jī)棧差不多,前者是為虛擬機(jī)使用到的Native方法提供內(nèi)存空間。有些虛擬機(jī)的實(shí)現(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堆(垃圾堆),里面存放的是幾乎所有的對(duì)象實(shí)例和數(shù)組數(shù)據(jù)。

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

2.5 方法區(qū)

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

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

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

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

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

類和接口的全限定名

字段的名稱和描述符

方法的名稱和描述符

屬于方法區(qū)一部分,所以和方法區(qū)一樣,會(huì)oom。


局部變量的基本數(shù)據(jù)類型和引用存儲(chǔ)于棧中,引用的對(duì)象實(shí)體存儲(chǔ)于堆中。

——因?yàn)樗鼈儗儆诜椒ㄖ械淖兞浚芷陔S方法而結(jié)束。

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

——因?yàn)樗鼈儗儆陬?,類?duì)象終究是要被new出來使用的。


我們說的內(nèi)存泄露,是針對(duì),也只針對(duì)堆內(nèi)存,他們存放的就是引用指向的對(duì)象實(shí)體。


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


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


確定對(duì)象是否活著的方法有:

1、引用計(jì)數(shù)算法

?????? 1.1算法分析

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

1.2優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

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

缺點(diǎn):

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

1引用計(jì)數(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指向的對(duì)象已經(jīng)不可能再被訪問,但是由于它們互相引用對(duì)方,導(dǎo)致它們的引用計(jì)數(shù)器都不為0,那么垃圾收集器就永遠(yuǎn)不會(huì)回收它們。



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


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

java中可作為GC Root的對(duì)象有

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

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

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

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

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

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


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

Reference項(xiàng)目的FinalizeEscapeGC


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

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

內(nèi)存泄漏

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

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

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

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


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

Java對(duì)引用的分類Strong

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

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

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

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

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


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


對(duì)于軟引用和弱引用的選擇,

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


內(nèi)存泄漏就是

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

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


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

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

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


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

因?yàn)樗馨l(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我們可以點(diǎn)擊

運(yùn)行app。

然后我們能看到


我們點(diǎn)擊memory后


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

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

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

④ 放大時(shí)間線的按鈕。

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

⑥ 事件時(shí)間線顯示活動(dòng)狀態(tài)、用戶輸入事件和屏幕旋轉(zhuǎn)事件。

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

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

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

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


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



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


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

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

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


但是說實(shí)話這個(gè)頁面想要分析出什么很難。一般不會(huì)使用這個(gè)頁面來進(jìn)行分析,最多打開看一眼剛剛我們進(jìn)入的Activity是否因?yàn)槲覀兺顺龆厥铡?/p>


先按照包名來分組,


Alloc Cout : 對(duì)象數(shù)

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

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


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



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

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


我們先把

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

AS自動(dòng)分析

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


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

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


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

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



Memory Analyzer Tool基于eclipse

可以直接下載:

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

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


點(diǎn)擊eclipse? ? marketplace...搜索memory。


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

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

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

hprof-conv -z src dst



然后在Mat中打開它:


打開我們的快照文件,


之后我們能看到


我們點(diǎn)擊


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

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

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


out查看這個(gè)對(duì)象持有的外部對(duì)象引用。

incoming查看這個(gè)對(duì)象被哪些外部對(duì)象引用。


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


排除軟弱虛引用。


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

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

解決辦法是:

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

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

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


結(jié)合代碼


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

在Activity中搜索Observer,可以找到很多個(gè)new Observer。

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


而這里只有注冊,沒有注銷。


我們在onDestory中加入注銷。


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

結(jié)果:

還有兩個(gè)Activity。

第一個(gè)問題仍然是observer,但是是在MessageFragment。

第二個(gè)問題也還是InputMethodmanager,但是泄漏點(diǎn)變了。

修改:


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

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


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

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


之后在mat中打開這個(gè)兩個(gè)文件并都切換到直方圖查看。

再然后把兩個(gè)直方均加入對(duì)比,點(diǎn)擊


或者


執(zhí)行對(duì)比:



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



然后能看到


第二行就是指第二個(gè)文件相比第一個(gè)文件多出來了幾個(gè)對(duì)象。

如果存在增加了不合理的對(duì)象,同樣可以查看其GC root。???

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

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

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

https://github.com/square/leakcanary

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


總結(jié):

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

1.集合類

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

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

2、靜態(tài)成員

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

2.單例模式

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


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

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


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

BraodcastReceiver,ContentObserver,F(xiàn)ileObserver,Cursor,Callback等在 Activity

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

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


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


那么現(xiàn)在這段代碼存在隱患的。因?yàn)槿绻\(yùn)行到fos2時(shí)候拋出了異常,那么fos也沒辦法close。

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


因?yàn)槿绻鹷rite發(fā)生異常那么這個(gè)fos會(huì)因?yàn)闆]有close造成內(nèi)存泄漏。

4.

Handler

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


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


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


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


5.

Thread 內(nèi)存泄露

和handler一樣,線程也是造成內(nèi)存泄露的一個(gè)重要的源頭。線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控。比如線程是 Activity 的內(nèi)部類,則線程對(duì)象中保存了 Activity 的一個(gè)引用,當(dāng)線程的 run 函數(shù)耗時(shí)較長沒有結(jié)束時(shí),線程對(duì)象是不會(huì)被銷毀的,因此它所引用的老的 Activity 也不會(huì)被銷毀,因此就出現(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容