Android內存泄漏研究

概念

oom是因爲分很多種 。
1、堆內存溢出。無法回收的對象達到了最大堆容量限制後,在創建對象,申請內存。就對oom。
2、直接內存溢出(native層);使用的是系統內存。如果是直接malloc或jdk自帶的native方法直接調用的是malloc(這種方法都是不對使用者開放的)直接申請內                              存,統內存不足,在去申請,就會直接oom。
如果是通過jdk提供的方法申請內存,拋出oom,他並沒有真正的向系統分配內存,是通過計算得出內存無法分配,手動拋出異常。(這時候可以捕獲異常,是釋放內存)。
這種oom,是因爲GC沒有去回收這些內存。(沒有觸發GC)。
導致溢出的關鍵是因爲 在 直接內存 申請內存 ,在垃圾回收進行時,雖然虛擬機會對 直接內存 進行回收。但是 直接內存 不能像,新生代,老年代那樣,發現內存控件不足了就通知收集器進行垃圾回收,他只能等待 老年代 滿了後 Full GC,然後 幫他清理掉內存的廢氣對象。 所以,明明堆內存有空間,也會oom。(這也是3.0之後把bitmap的放到了堆中)。


3、方法區內存溢出。 存儲的是虛擬機加載的類信息,常量,靜態變量、即時編譯後的代碼等數據。回收不了的信息沾滿了就會oom。
客戶端 動態加載的類,都存在這裏,佔用的空間也不容忽視。而且現在做的,加載了之後就不管了,很有可能會導致溢出。
我認爲應該添加處理類卸載的功能。能夠讓GC回收它。


4、虛擬機棧和方法棧溢出.


所以產生oom原因。大家也瞭解了。 還是應該從代碼入手。或者瞭解第三方的實現,能夠很好的管理。

根搜索算法

Android虛擬機的垃圾回收採用的是根搜索算法。GC會從根節點(GC Roots)開始對heap進行遍歷。到最後,部分沒有直接或者間接引用到GC Roots的就是需要回收的垃圾,會被GC回收掉。

根搜索算法相比引用計數法很好的解決了循環引用的問題。舉個例子,Activity有View的引用,View也有Activity的引用,之前我還嘗試去源代碼裏找Activity何時和View斷開連接是大錯特錯了。當Activity finish掉之後,Activity和View的循環引用已成孤島,不再引用到GC Roots,無需斷開也會被回收掉。

內存泄漏

Android內存泄漏指的是進程中某些對象(垃圾對象)已經沒有使用價值了,但是它們卻可以直接或間接地引用到gc roots導致無法被GC回收。無用的對象佔據着內存空間,使得實際可使用內存變小,形象地說法就是內存泄漏了。

場景

  • 類的靜態變量持有大數據對象
    靜態變量長期維持到大數據對象的引用,阻止垃圾回收。
  • 非靜態內部類的靜態實例
    非靜態內部類會維持一個到外部類實例的引用,如果非靜態內部類的實例是靜態的,就會間接長期維持着外部類的引用,阻止被回收掉。
  • 資源對象未關閉
    資源性對象如Cursor、File、Socket,應該在使用後及時關閉。未在finally中關閉,會導致異常情況下資源對象未被釋放的隱患。
  • 註冊對象未反註冊
    未反註冊會導致觀察者列表裏維持着對象的引用,阻止垃圾回收。
  • Handler臨時性內存泄露
    Handler通過發送Message與主線程交互,Message發出之後是存儲在MessageQueue中的,有些Message也不是馬上就被處理的。在Message中存在一個 target,是Handler的一個引用,如果Message在Queue中存在的時間越長,就會導致Handler無法被回收。如果Handler是非靜態的,則會導致Activity或者Service不會被回收。
    由於AsyncTask內部也是Handler機制,同樣存在內存泄漏的風險。
    此種內存泄露,一般是臨時性的。

預防

  • 不要維持到Activity的長久引用,對activity的引用應該和activity本身有相同的生命週期。
  • 儘量使用context-application代替context-activity
  • Activity中儘量不要使用非靜態內部類,可以使用靜態內部類和WeakReference代替。

檢測

靜態檢測

靜態檢測主要是檢測資源未關閉的情況,Eclipse和Android Studio都可以檢測出IO或者Socket未關閉的情況,然後在finally中關閉即可。

動態監測

動態檢測主要是依靠MAT這個工具。2011年Google IO有一個主題演講,非常詳細地講解了內存泄露的檢測,包含MAT工具的使用,值得一看。
我在某項目中使用MAT檢測,發現一處內存泄漏,分享一下過程。
從首頁到商戶列表到商戶詳情再退回首頁執行Dump HPROF File,查看MAT中的Histogram,過濾Activity後結果如下:HistogramHistogram
仍然存在ShopInfoActivity的實例,選中右鍵點擊Merge Shortest Paths to GC Roots,結果如下:PathPath
可以看到ShopDatabase中維持着ShopInfoActivity的引用,查看源代碼如下:


public class ShopDatabase {

private static ShopDatabase instance;
public static ShopDatabase getInstance(Context context) {
if (instance == null && context != null) {
instance = new ShopDatabase(context);
}
return instance;
}
protected Context context;

}
很明顯,靜態變量instance長期持有context的引用,造成內存泄露。
所以動態檢測內存泄露的一個簡單思路就是隨意操作APP,最後返回首頁,然後用MAT檢測,查看是否存在Activity多於一個或者Activity不正常存在的問題。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章