天天看點

安卓手機記憶體洩漏問題的簡單介紹

記憶體抖動是啥?記憶體持續升高是啥?記憶體洩漏又是啥?記憶體溢出又是啥?會導緻什麼問題?哪些問題導緻的?如何檢測?有哪些解決辦法?系統内是否有對應機制?

首先,簡單介紹一下安卓APP應用的運作原理:為了能夠使Android應用程式能夠高效快速地運作,是以Android的每一個應用程式都會有一個專有的Davilk虛拟機執行個體對象來運作,這個Davilk對象是由Zygote服務程序孵化出來的,這樣的機制使每個應用程序都隻能在自己的程序空間内運作。簡而言之,就是一個APP-》一個Dalvik虛拟機執行個體-》程序啟動-》建立一個UI線程。

再者,介紹一下記憶體的相關知識:

衆所周知的Java是在JVM所虛拟的記憶體環境中運作的,JVM的記憶體可分為三個區:棧(stack)、堆(heap)、方法區(method).

1.棧(stack),一種資料結構,學過資料結構的都知道它最顯著的特點就是“後進先出”,但棧裡面隻存放基本類型和對象的引用(不存放對象,隻存放對象的引用)

2.堆(heap):堆記憶體用于存放由new建立的對象和數組。在堆中配置設定的記憶體,是由JAVA虛拟機自動垃圾回收管理器來管理。JVM隻有一個堆區被所有的線程共享,堆中不存放基本類型和對象引用,隻存放對象本身。

3.方法區(method):又叫靜态區。被所有線程共享,包含所有的class及static變量。

那對象的引用又有哪些???

對象的引用分為以下四種(按級别劃分):

1.強引用(Strong reference):實際編碼中最常見的一種引用類型。常見形式如:A a =new A();等。強引用本身存儲在棧記憶體中,其存儲指向記憶體中對象的位址。一般情況下,當對記憶體中的對象不再有任何強引用指向它時,垃圾回收機制開始考慮可能要對此記憶體進行垃圾回收。如當進行編碼:a=null,此時,剛剛在堆中配置設定位址并建立的a對象沒有其他的任何引用,當系統進行垃圾回收時,堆記憶體将垃圾回收。

2.軟引用(Soft Reference):軟引用的一般使用形式如下:

A a=new A();

SoftReference<A> srA=new SoftReference<A>(a);

軟引用所訓示的對象進行垃圾回收需滿足以下條件:

 1.當其訓示的對象沒有任何強引用對象指向它

 2.當虛拟機記憶體不足時。

3.弱引用(WeakReference):弱引用的一般格式:

A a=new A();

WeakReference<A> srA=new WeakReference<A>(a);

 WeakReference不改變原有強引用對象的垃圾回收時機,一旦其訓示對象沒有任何強引用對象時,此對象即進入正常的垃圾回收流程。

4.虛引用(Phantom Reference):

回到主題,來介紹一下這幾個概念:

記憶體抖動:大量的對象被建立又在短時間内被釋放。如下圖(使用Mermory Monitor檢測)

安卓手機記憶體洩漏問題的簡單介紹

記憶體持續升高:APP在運作的過程中占用的記憶體值持續升高。當其記憶體升高到超出記憶體所給的,就會造成記憶體溢出。類似如下圖

安卓手機記憶體洩漏問題的簡單介紹

記憶體洩漏:指程式中己動态配置設定的堆記憶體由于某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導緻程式運作速度減慢甚至系統崩潰等嚴重後果。會造成程式卡頓甚至崩潰。如果持有對象的強引用,垃圾回收機制是無法在記憶體中回收這個對象的。

記憶體溢出(OOM):程式向系統申請的記憶體空間超出了系統能給的。PS:大量的記憶體洩漏會導緻記憶體溢出進而導緻程式崩潰。

造成記憶體洩漏的主要原因是:持有對象的強引用且并沒有及時釋放,進而造成記憶體單元一直被占用,浪費記憶體空間。

垃圾回收機制:垃圾回收(garbage collection,簡稱GC)可以自動清空堆中不再使用的對象。在JAVA中對象是通過引用使用的。如果再沒有引用指向該對象,那麼該對象就無從處理或調用該對象,這樣的對象稱為不可到達(unreachable)。垃圾回收用于釋放不可到達的對象所占據的記憶體。

那排查記憶體洩漏的一般步驟有哪些???

排查記憶體洩漏步驟:

通過統計平台了解OOM情況->重制問題->在發生記憶體洩漏時Dump記憶體->在記憶體分析工具中反複檢視,找到原本該被回收掉的對象->計算此對象到GC roots的最短強引用路徑->确定并修複問題

來介紹一下幾種常見的造成記憶體洩漏的情況及對應的解決辦法:

單例造成的記憶體洩漏:

單例的靜态特性導緻其生命周期同應用一樣長。有時建立單例時如果我們需要Context對象,如果傳入的是Application的Context那麼不會有問題。如果傳入的是Activity的Context對象,那麼當Activity生命周期結束時,該Activity的引用依然被單例持有,是以不會被回收,而單例的生命周期又是跟應用一樣長,是以這就造成了記憶體洩露。

非靜态内部類建立靜态執行個體造成的記憶體洩漏:

在 Java 中,非靜态内部類(包括匿名内部類,比如 Handler, Runnable匿名内部類最容易導緻記憶體洩露)會持有外部類對象的強引用(如 Activity),而靜态的内部類則不會引用外部類對象。非靜态内部類或匿名類因為持有外部類的引用,是以可以通路外部類的資源屬性成員變量等;靜态内部類不行。因為普通内部類或匿名類依賴外部類,是以必須先建立外部類,再建立普通内部類或匿名類;而靜态内部類随時都可以在其他外部類中建立。

Handler造成的記憶體洩漏:

在Android開發中,我們經常會使用Handler來控制主線程UI程式的界面變化,使用非常簡單友善,但是稍不注意,很容易引發記憶體洩漏。我們知道,Handler、Message、MessageQueue是互相關聯在一起的,Handler通過發送消息Message與主線程進行互動,如果Handler發送的消息Message尚未被處理,該Message及發送它的Handler對象将被MessageQueue一直持有,這樣就可能會導緻Handler無法被回收。

線程造成的記憶體洩漏:

AsyncTask和Runnable都使用了匿名内部類,那麼它們将持有其所在Activity的隐式引用。如果任務在Activity銷毀之前還未完成,那麼将導緻Activity的記憶體資源無法被回收,進而造成記憶體洩漏。

資源未關閉造成的記憶體洩漏:

1. BroadcastReceiver,ContentObserver 之類的沒有解除注冊

2. Cursor,Stream 之類的沒有 close

3. 無限循環的動畫在 Activity 退出前沒有停止

4.一些其他的該 release 的沒有 release,該 recycle 的沒有 recycle… 等

使用ListView時造成的記憶體洩漏 :

初始時ListView會從BaseAdapter中根據目前的螢幕布局執行個體化一定數量的View對象,同時ListView會将這些View對象緩存起來。當向上滾動ListView時,原先位于最上面的Item的View對象會被回收,然後被用來構造新出現在下面的Item。這個構造過程就是由getView()方法完成的,getView()的第二個形參convertView就是被緩存起來的Item的View對象(初始化時緩存中沒有View對象則convertView是null)。

集合容器中的記憶體洩露:

我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當我們不需要該對象時,并沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。

WebView造成的洩露:

Android 中的 WebView 存在很大的相容性問題,有些 WebView 甚至存在記憶體洩露的問題。

解決辦法:

為 WebView 開啟另外一個程序,通過 AIDL 與主程序進行通信, WebView 所在的程序可以根據業務的需要選擇合适的時機進行銷毀,進而達到記憶體的完整釋放。

那我們可以使用哪些工具來進行記憶體洩漏的檢測???

介紹幾種常見的記憶體洩漏工具:

Mermory Monitor:

位于Android Monitor中,該工具可以:

1. 友善的顯示記憶體使用情況以及情況

2. 快速定位卡頓是否與GC有關

3. 快速定位潛在的記憶體使用問題(記憶體占用一直在增長)

4. (缺點)不能準确的定位問題

Allocation Tracker:

該工具用途:

1. 可以定位代碼中配置設定的對象類型、大小、時間、線程、堆棧等資訊

2. 可以定位記憶體抖動問題

3. 配合HeapViewer定位記憶體洩漏問題(可以找出洩漏的對象是在哪裡建立等等)

使用方法:在Mermory Monitor點選Start Allocation Tracking按鈕即可開始跟蹤在點選停止跟蹤後會顯示統計結果。

Heap Viewer:

該工具用途:

1. 顯示記憶體快照資訊

2. 每次GC後收集一次資訊

3. 查找記憶體洩漏的利器

使用方法:在Mermory Monitor點選Dump Java Heap 按鈕,在統計報告中選package分類。配合Mermory Monitor的initiate GC按鈕,可檢測記憶體洩漏的情況。

Leak Canary:(被譽為記憶體洩漏檢測神器,也是使用最廣的工具)

一個在調試時就可以檢測記憶體洩露的Java開源庫它可以在我們的應用發生記憶體洩漏的時候發出提醒,提醒包括通知和Log。除了會在界面中顯示洩漏資訊之外,Log中也一樣會輸出洩漏的具體資訊。除了能夠發現并修複APP中的記憶體洩漏問題還可以發現一些Android SDK自身的記憶體洩漏問題,更加有效減少OOM錯誤。

LAST  and the LAST,如何避免記憶體洩漏

1、在涉及使用Context時,對于生命周期比Activity長的對象應該使用Application的Context。凡是使用Context優先考慮Application的Context,當然它并不是萬能的,對于有些地方則必須使用Activity的Context。對于Application,Service,Activity三者的Context的應用場景如下:

安卓手機記憶體洩漏問題的簡單介紹

其中,NO1表示Application和Service可以啟動一個Activity,不過需要建立一個新的task任務隊列。而對于Dialog而言,隻有在Activity中才能建立。除此之外三者都可以使用。

2、對于需要在靜态内部類中使用非靜态外部成員變量(如:Context、View ),可以在靜态内部類中使用弱引用來引用外部類的變量來避免記憶體洩漏。

3、對于不再需要使用的對象,顯示的将其指派為null,比如使用完Bitmap後先調用recycle(),再賦為null。

4、保持對對象生命周期的敏感,特别注意單例、靜态對象、全局性集合等的生命周期。

5、對于生命周期比Activity長的内部類對象,并且内部類中使用了外部類的成員變量,可以這樣做避免記憶體洩漏:

1)将内部類改為靜态内部類

2)靜态内部類中使用弱引用來引用外部類的成員變量

參考連結:https://www.jianshu.com/p/bf159a9c391a