概念
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後結果如下:
仍然存在ShopInfoActivity
的實例,選中右鍵點擊Merge Shortest Paths to GC Roots
,結果如下:
可以看到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不正常存在的問題。