內存泄露原因
由於某些資源應該被釋放卻沒有被釋放,一直被某些對象佔用,導致GC不能正常回收資源。
JVM內存分配策略
java運行時的內存分配策略有:靜態分配、棧式分配、堆式分配。
不同的分配策略對應不同的內存區:
靜態內存區:主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,並且在程序整個運行期間都存在。
棧區:當方法被執行時,方法體內的局部變量都在棧上創建,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。因爲棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
堆區:又稱動態內存分配,通常就是指在程序運行時直接 new 出來的內存。這部分內存在不使用時將會由 Java 垃圾回收器來負責回收。
局部變量的基本數據類型和引用存儲於棧中,引用的對象實體存儲於堆中。—— 因爲它們屬於方法中的變量,生命週期隨方法而結束。
成員變量全部存儲與堆中(包括基本數據類型,引用和引用的對象實體)—— 因爲它們屬於類,類對象終究是要被new出來使用的。
Java的內存管理
Java的內存管理,就是內存的分配與釋放問題。
分配是程序中通過new等關鍵字獲取的,釋放是GC做的。JVM需要維護一套所有對象的引用關係,所以JVM的運行速度會有些慢。監視對象狀態是爲了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。
常見的泄露類型
1、集合類泄露:只add,沒有進行remove
2、單例:與應用生命週期同樣長,使用不當會造成泄露。單例時傳入的context應該是applicationcontext,如果是activitycontext,會造成內存泄露。
3、非靜態內部類:靜態內部類會持有外部類的引用。應該將非靜態內部類抽出來。
4、匿名內部類 :被異步線程持有。
5、Handler 造成的內存泄漏:很多時候我們爲了避免 ANR 而不在主線程進行耗時操作,在處理網絡任務或者封裝一些請求回調等api都藉助Handler來處理,但 Handler 不是萬能的,對於 Handler 的使用代碼編寫一不規範即有可能造成內存泄漏。另外,我們知道 Handler、Message 和 MessageQueue 都是相互關聯在一起的,萬一 Handler 發送的 Message 尚未被處理,則該 Message 及發送它的 Handler 對象將被線程 MessageQueue 一直持有。
由於 Handler 屬於 TLS(Thread Local Storage) 變量, 生命週期和 Activity 是不一致的。因此這種實現方式一般很難保證跟 View 或者 Activity 的生命週期保持一致,故很容易導致無法正確釋放。
6、儘量避免使用 static 成員變量
綜述,即推薦使用靜態內部類 + WeakReference 這種方式。每次使用前注意判空。
前面提到了 WeakReference,所以這裏就簡單的說一下 Java 對象的幾種引用類型。
Java對引用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種
7、儘量避免使用 static 成員變量
以上,參考https://blog.csdn.net/u013495603/article/details/50696170
檢測工具
- 功能強大PC端檢測工具,如
MemoryAnalyzer
運行在PC端抓取Android手機中的dump文件進行深度分析。 http://www.eclipse.org/mat/ - 小而優的Android端檢測工具,如
LeakCanary
隨App一起安裝會在Android手機桌面安裝的內存泄露檢測App https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
編程注意點:
- 對 Activity 等組件的引用應該控制在 Activity 的生命週期之內; 如果不能就考慮使用
getApplicationContext 或者 getApplication,以避免 Activity
被外部長生命週期的對象引用而泄露。 - 儘量不要在靜態變量或者靜態內部類中使用非靜態外部成員變量(包括context
),即使要使用,也要考慮適時把外部成員變量置空;也可以在內部類中使用弱引用來引用外部類的變量。 對於生命週期比Activity長的內部類對象,並且內部類中使用了外部類的成員變量,可以這樣做避免內存泄漏:
將內部類改爲靜態內部類靜態內部類中使用弱引用來引用外部類的成員變量
Handler 的持有的引用對象最好使用弱引用,資源釋放時也可以清空 Handler 裏面的消息。比如在 Activity onStop
或者 onDestroy 的時候,取消掉該 Handler 對象的 Message和 Runnable.在 Java 的實現過程中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地將此對象賦值爲 null,比如使用完Bitmap
後先調用 recycle(),再賦爲null,清空對圖片等資源有直接引用或者間接引用的數組(使用 array.clear() ;
array = null)等,最好遵循誰創建誰釋放的原則。正確關閉資源,對於使用了BraodcastReceiver,ContentObserver,File,遊標
Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷。保持對對象生命週期的敏感,特別注意單例、靜態對象、全局性集合等的生命週期。