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

Part 1

在長久以來的 Android 開發(fā)過程中,內(nèi)存泄漏一直是一個比較頭疼的問題。內(nèi)存泄漏會導致應用卡頓,用戶體驗不佳,甚至會造成應用崩潰的嚴重后果。所以如何科學地進行內(nèi)存管理一直是大家探討的話題,從一開始主動使用 MAT 分析 hprof 文件,到后來 LeakCanary “被動”的接收內(nèi)存泄漏消息。應用中發(fā)現(xiàn)內(nèi)存泄漏的手段越來越多了,操作也越來越便捷,但內(nèi)存泄漏的問題還是不能輕易忽視的,提高應用的體驗和質(zhì)量也是迫在眉睫。

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

Part 2

內(nèi)存泄漏簡單粗俗的講,就是該被釋放的對象沒有釋放,一直被某個或某些實例所持有卻不再被使用導致 GC 不能回收。我們所說的內(nèi)存泄露是針對于堆內(nèi)存而言,堆內(nèi)存中存放的就是引用指向的對象實體。

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

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

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

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

堆區(qū):亦稱動態(tài)內(nèi)存分配。程序在運行的時候用malloc或new申請任意大小的內(nèi)存,程序員自己負責在適當?shù)臅r候用free或delete釋放內(nèi)存(Java則依賴垃圾回收器)。動態(tài)內(nèi)存的生存期可以由我們決定,如果我們不釋放內(nèi)存,程序?qū)⒃谧詈蟛裴尫诺魟討B(tài)內(nèi)存。 但是,良好的編程習慣是:如果某動態(tài)內(nèi)存不再使用,需要將其釋放掉。
接下來我們集中說下堆和棧的區(qū)別:

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

堆內(nèi)存用于存放所有由new創(chuàng)建的對象(內(nèi)容包括該對象其中的所有成員變量)和數(shù)組。在堆中分配的內(nèi)存,由java虛擬機自動垃圾回收器來管理。在堆中產(chǎn)生了一個數(shù)組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等于數(shù)組或者對象在堆內(nèi)存中的首地址,在棧中的這個特殊的變量就變成了數(shù)組或者對象的引用變量,以后就可以在程序中使用棧內(nèi)存中的引用變量來訪問堆中的數(shù)組或者對象,引用變量相當于為數(shù)組或者對象起的一個別名,或者代號。

堆是不連續(xù)的內(nèi)存區(qū)域(因為系統(tǒng)是用鏈表來存儲空閑內(nèi)存地址,自然不是連續(xù)的),堆大小受限于計算機系統(tǒng)中有效的虛擬內(nèi)存(32bit系統(tǒng)理論上是4G),所以堆的空間比較靈活,比較大。棧是一塊連續(xù)的內(nèi)存區(qū)域,大小是操作系統(tǒng)預定好的,windows下棧大小是2M(也有是1M,在編譯時確定,VC中可設置)。

對于堆,頻繁的new/delete會造成大量內(nèi)存碎片,使程序效率降低。對于棧,它是先進后出的隊列,進出一一對應,不產(chǎn)生碎片,運行效率穩(wěn)定高。

說了這么多了,我們來看一個例子吧:

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 指向的對象實體存放在堆中;

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

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

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

局部變量的基本數(shù)據(jù)類型和引用存儲于棧中,引用的對象實體存儲于堆中。—— 因為它們屬于方法中的變量,生命周期隨方法而結(jié)束。
成員變量全部存儲與堆中(包括基本數(shù)據(jù)類型,引用和引用的對象實體)—— 因為它們屬于類,類對象終究是要被new出來使用的。

Part 3

那么有沒有想過,內(nèi)存為什么會泄露?

Java的內(nèi)存垃圾回收機制是從程序的主要運行對象(如靜態(tài)對象/寄存器/棧上指向的堆內(nèi)存對象等)開始檢查引用鏈,當遍歷一遍后得到上述這些無法回收的對象和他們所引用的對象鏈,組成無法回收的對象集合,而其他孤立對象(集)就作為垃圾回收。GC為了能夠正確釋放對象,必須監(jiān)控每一個對象的運行狀態(tài),包括對象的申請、引用、被引用、賦值等,GC都需要進行監(jiān)控。監(jiān)視對象狀態(tài)是為了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。

在Java中,這些無用的對象都由GC負責回收,因此程序員不需要考慮這部分的內(nèi)存泄露。雖然,我們有幾個函數(shù)可以訪問GC,例如運行GC的函數(shù)System.gc(),但是根據(jù)Java語言規(guī)范定義,該函數(shù)不保證JVM的垃圾收集器一定會執(zhí)行。因為不同的JVM實現(xiàn)者可能使用不同的算法管理GC。通常GC的線程的優(yōu)先級別較低。JVM調(diào)用GC的策略也有很多種,有的是內(nèi)存使用到達一定程度時,GC才開始工作,也有定時執(zhí)行的,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來說,我們不需要關(guān)心這些。

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

20190629134830.png

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

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

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

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

Part 4

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

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

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

Part 5

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

  • MAT
  • Android Profiler
  • LeakCanary

Part 6

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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