Android 非靜態(tài)內(nèi)部類導致內(nèi)存泄漏

內(nèi)存泄漏

一個不會被使用的對象,因為另一個正在使用的對象持有該對象的引用,導致它不能正常被回收,而停留在堆內(nèi)存中。

內(nèi)存泄漏的危害

最壞的情況,App可能會因為大量的內(nèi)存泄漏而導致內(nèi)存耗盡,引發(fā)Crash,如果內(nèi)存未耗盡,App也會猶豫內(nèi)存空間不足,出現(xiàn)頻繁的GC(垃圾回收),每次一出GC都是非常耗時的阻塞性操作,會造成設(shè)備非常嚴重的卡頓,給用戶的體驗就是,手機無論做什么操作,都是卡的,這也是Android設(shè)備玩久了之后常見的現(xiàn)象。

泄漏代碼(案例)

image.png

在sendGoodsMessage方法中,使用了一個非靜態(tài)匿名內(nèi)部類IMValueCallback,而這個非靜態(tài)匿名內(nèi)部類對其外部類存在一個隱式引用,其外部類在銷毀之前,如果該非靜態(tài)內(nèi)部類的sendMessage異步任務(wù)還未完成,將會導致外部類的內(nèi)存資源無法正常釋放,造成了內(nèi)存泄漏。

所以這個問題總結(jié)為: 非靜態(tài)內(nèi)部類中線程生命周期不可控,能否正常回收完全由線程的生命周期決定。如果線程是永久運行的,那么將永遠無法釋放,因為在Java中線程是垃圾回收機制的根源,在運行系統(tǒng)中DVM虛擬機總會硬件持有所有運行狀態(tài)的進程的引用,結(jié)果導致處于運行狀態(tài)的線程將永遠不會被回收。

非靜態(tài)內(nèi)部類還有一種的情況的內(nèi)存泄漏

非靜態(tài)內(nèi)部類中創(chuàng)建了一個靜態(tài)實例,導致該實例的生命周期和應(yīng)用ClassLoader級別,又因為該靜態(tài)實例又會隱式持有其外部類的引用,所以導致其外部類無法正常釋放,出現(xiàn)了泄漏問題。

深入分析

針對非靜態(tài)內(nèi)部類引發(fā)的內(nèi)存泄漏這個點,進行深入分析

為什么非靜態(tài)內(nèi)部類對外部類會存在一個隱式引用? 為什么非靜態(tài)內(nèi)部類中存在異步任務(wù),可能會導致其對應(yīng)的外部類內(nèi)存資源無法正常釋放? 為什么非靜態(tài)內(nèi)部類中創(chuàng)建了一個靜態(tài)實例,會導致內(nèi)存泄漏?

深入剖析-隱式引用

image.png

為什么在非靜態(tài)匿名內(nèi)部類中,我們可以訪問到外部類的testMethod方法?這就是隱式引用的作用。

通過編寫一個簡單的測試代碼,進行深入的分析

源碼和編譯后的字節(jié)碼">非靜態(tài)匿名內(nèi)部類的源碼和編譯后的字節(jié)碼
image.png
image.png

args_size:這個代表著隱式引用的個數(shù), 可以看出非靜態(tài)匿名內(nèi)部類中確實持有外部類的引用。

非靜態(tài)內(nèi)部類的源碼和編譯后的字節(jié)碼
image.png
image.png
靜態(tài)內(nèi)部類的源碼和編譯后的字節(jié)碼
image.png
image.png

結(jié)論:非靜態(tài)內(nèi)部類和非靜態(tài)匿名內(nèi)部類中確實都持有外部類的引用, 靜態(tài)內(nèi)部類中未持有外部類的引用
反編譯線上版本, c.class 這個就是MessagePresender類的字節(jié)碼

image.png

可以發(fā)現(xiàn),在編譯器編譯過程中,幫我們隱式的傳入了this這個參數(shù),這也是為什么,我們平時在方法中能使用this這個關(guān)鍵字的原因。

了解了隱式引用,那么為什么它會是導致內(nèi)存泄漏的根本原因? 這里又得說明一下,虛擬機的垃圾回收策略。

java垃圾回收機制

垃圾回收機制:Java采用根搜索算法,當GC Roots不可達時,并且對象finalize沒有自救的情況下,才會回收。

回收對象:GC會收集那些不是GC roots且沒有被GC roots引用的對象。

image.png

而這些引用就是對象之間的連線,垃圾回收的判定條件就在這些連線上,更詳細的說明就不在這里描述,有興趣的自行Google,或者查閱《深入理解Java虛擬機》。

解決方案

通過上述的分析,要預防非靜態(tài)內(nèi)部類的泄漏問題,就得管理好對象間的引用關(guān)系。

解決思路

去除隱式引用(通過靜態(tài)內(nèi)部類來去除隱式引用) 手動管理對象引用(修改靜態(tài)內(nèi)部類的構(gòu)造方式,手動引入其外部類引用) 當內(nèi)存不可用時,不執(zhí)行不可控代碼(Android可以結(jié)合智能指針,WeakReference包裹外部類實例)

image.png

最后

并不是所有的內(nèi)部類只能使用靜態(tài)內(nèi)部類,只有在該內(nèi)部類中的生命周期不可控制的情況下,我們要采用靜態(tài)內(nèi)部類,其它時候大家可以照舊。

如果了解了這些,比如Context、Handler、Timer、靜態(tài)Acitivity、靜態(tài)View、Thread等等造成的泄漏,也是能融會貫通的,只要大家多想一下它們之間的引用關(guān)系即可。

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

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

  • 一.Java內(nèi)存分配結(jié)構(gòu)復習 1.Java內(nèi)存分配策略 上一篇Android內(nèi)存管理分析總結(jié)中我們提到了Java內(nèi)...
    Geeks_Liu閱讀 912評論 5 7
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    _痞子閱讀 1,700評論 0 8
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    apkcore閱讀 1,306評論 2 7
  • 堅持讀書打卡第四天。 提倡健康思維快樂人生,促使對方勵志向上。
    冰雪連閱讀 187評論 0 0
  • 一本書,一支鋼筆,一張椅子,一張桌子,一瓶墨水,依然還是四年前的原班人馬。人生就是這樣,不論是處于何種境地...
    驢子自駕游閱讀 933評論 8 10

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