Android性能優(yōu)化之常見的內存泄漏

Java內存分配策略

java程序運行時的內存分配策略有三種,分別是靜態(tài)分配,棧式分配,堆式分配。對應的三種存儲策略使用的內存空間主要分別是靜態(tài)存儲區(qū)(方法區(qū)),棧區(qū),堆區(qū)。

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

棧區(qū):當方法被執(zhí)行時,方法體內的局部變量都在棧上創(chuàng)建,并在方法執(zhí)行結束時這些局部變量所持有的內存將會被自動釋放。

堆區(qū):程序運行時直接new出來的內存,這部分內存在不使用時將會由gc來負責回收。

Java四種引用

強引用:JVM 寧可拋出 OOM ,也不會讓 GC 回收具有強引用的對象;

軟引用:只有在內存空間不足時,才會被回的對象;

弱引用:在 GC 時,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存;

虛引用:任何時候都可以被GC回收

我們常說的內存泄漏是指new出來的Object無法被GC回收,即為強引用

Android常見的內存泄漏案例

單例造成的內存泄漏

由于單例靜態(tài)特性使得單例的生命周期和應用的生命周期一樣長。如果一個對象已經不需要使用了,而單例對象還持有該對象的引用,那么這個對象將不能被正?;厥眨瑢е铝藘却嫘孤?。如下這個典例:

單例造成的內存泄漏

這是一個普通的單例模式,當創(chuàng)建這個單例的時候,由于需要傳入一個Context,所以這個Context的生命周期的長短至關重要:

1、傳入的是Application的Context:這將沒有任何問題,因為單例的生命周期和Application的一樣長

2、傳入的是Activity的Context:當這個Context所對應的Activity退出時,由于單例對象還持有該Activity的引用,該Activity不能被正常回收。

所以正確的單例應該修改為下面這種方式:

修改后的單例

這樣不管傳入什么Context最終將使用Application的Context,而單例的生命周期和應用的一樣長,這樣就防止了內存泄漏。

二、非靜態(tài)內部類創(chuàng)建靜態(tài)實例造成的內存泄漏

有時候我們可能會出現(xiàn)這樣的寫法

非靜態(tài)內部類創(chuàng)建靜態(tài)實例

這樣就在Activity內部創(chuàng)建了一個非靜態(tài)內部類的單例,每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復創(chuàng)建,不過這種寫法卻會造成內存泄漏,因為非靜態(tài)內部類默認會持有外部類的引用,而又使用了該非靜態(tài)內部類創(chuàng)建了一個靜態(tài)的實例,該實例的生命周期和應用的一樣長,這就導致了該靜態(tài)實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。

正確的做法為:將該內部類設為靜態(tài)內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,使用ApplicationContext

Handler造成的內存泄漏

Handler的使用造成的內存泄漏問題應該說最為常見了,平時在處理網(wǎng)絡任務或者封裝一些請求回調等api都應該會借助Handler來處理,對于Handler的使用代碼編寫一不規(guī)范即有可能造成內存泄漏,如下示例:

Handler造成的內存泄漏

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

處理Handler造成的內存泄漏

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

移除消息隊列中的消息

使用mHandler.removeCallbacksAndMessages(null);是移除消息隊列中所有消息和所有的Runnable。

四、資源未關閉造成的內存泄漏

對于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存泄漏。

10 條提升 Android 性能的建議

https://academy.realm.io/cn/posts/droidcon-farber-improving-android-app-performance/

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容