若是你參加面試,面試官常常會問到你的一個問題多是:你在開發過程當中,有過排除內存泄漏的經驗嗎?對於一個合格的Android/C/Java開發老手,這個問題想必已經深刻你的心;如果一名新手或者一直對內存泄漏這個東西模模糊糊的工程師,你的答案可能讓面試官並不滿意,這裏將從底到上對內存泄漏的緣由、排查方法和一些經驗爲你作一次完整的解剖。html
處理內存泄漏的問題是將軟件作到極致的一個必須的步驟,尤爲是那種將被用戶高強度使用的軟件。java
一個簡單的C和Android的例子jquery
一個最簡單的C的內存泄漏的例子:android
char *ptr1 = (char *)malloc(10); char *ptr2 = (char *)malloc(10); ptr2 = ptr1; free(ptr1)
這裏最後發生了10個字節的內存泄漏,那麼到底發生了什麼?程序員
首先各自分配了兩塊10個字節的內存,分別用叫ptr1和ptr2的指針指向這兩塊內存(就像是java中的引用),而後呢讓ptr2也指向一開始ptr1指向的那塊內存(這時候ptr1和ptr2都指向了ptr1一開始指向的那個10個字節的內存),最後用free將ptr1指向的那塊內存給釋放了——>結果就是一開始ptr2指向的那塊內存發生了泄漏(沒人用了卻又回收不掉)面試
可能你會說Java有內存垃圾回收機制,不要考慮誰分配和釋放的訪問,那麼下面這個例子就會讓你明白你錯了:網絡
public class PendingOrderManager { private static PendingOrderManager instance; private Context mContext; public PendingOrderManager(Context context) { this.mContext = context; } public static PendingOrderManager getInstance(Context context) { if (instance == null) { instance = new PendingOrderManager(context); } return instance; }
public void func(){
...
}
... }
而後讓你的某個Activity去使用這個PendingOrderManager單例,而且某個時候退出這個Activity:app
//belong to some Activity PendingOrderManager.getInstance(this).func(); ... finish()
這個時候內存泄漏已經發生:你退出了你的這個Activity本覺得java的垃圾回收會將它釋放,但實際上Activity一直被PendingOrderManager持有着。Acitivity這個Context被長生命週期個體(單例一旦被建立就是整個app的生命週期)持有致使了這個Context發生了內存泄漏。異步
這個例子和上面的例子是相通的,上面的C的例子由於忘記了手動執行free一個10字節內存致使內存泄漏。而下面這個例子是垃圾回收機制「故意忘記」了回收Context的內存而致使了內存泄漏。下面兩節將對這個裏面到底發生了什麼進行說明。函數
靜態、堆和棧
編譯原理說軟件內存分配的時候通常會放在三種位置:靜態存儲區域、堆和棧,他們的位置、功能、速度都各不相同,區別以下:
從上面便可看出靜態存儲區域是編譯時已經分配好的,棧是CPU自動控制的,那麼咱們所討論的內存泄漏的問題實際上就是分配在堆裏面的內存出現了問題,通常問題在於兩點:
Java有了垃圾回收(GC)爲何任而後內存泄漏
在Java中,內存的分配是由程序完成的,而內存的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程序員不須要經過調用函數來釋放內存,但它只能回收無用而且再也不被其它對象引用的那些對象所佔用的空間。可是誤判是常常發生的,有些內存實際上已經沒有用處了,可是GC並不知道。這裏簡單介紹下GC的機制:
上面一節說過棧上的局部變量能夠引用堆上的分配的內存,因此GC發生的時候,通常是遍歷一下靜態存儲區、棧從而列出全部堆上被他們引用的內存(對象)集合,這些內存都是有個引用計數,那麼除此以外,其餘的內存就是沒有被引用的(或者說引用計數歸零),這些內存就是要被釋放的,隨後GC開始清理這些內存(對象)
那麼這裏第一節的兩個例子就很好理解了,那個單例模式因爲生命週期太長(能夠把他看做一個虛擬的棧中的局部變量)而且一直引用了Context(即Activity),因此GC的時候發現這個Activity的引用計數仍是大於1,因此回收內存的時候把他跳過,但實際上咱們已經不須要這塊內存了。這樣就致使了內存泄漏。
Android使用弱引用和完美退出app的方法
從上面來看,內存泄漏由於對象被別人引用了而致使,java爲了不這種問題(假如你的單例模式必需要傳入個Context),特意提供了幾個特殊引用類型,其中一個叫作弱引用WeakReference,當它引用一個對象的時候,即便該WeakReference的生命週期更長,可是隻要發生GC,它就當即釋放所被引用的內存而不會繼續持有。
這裏有一個經常使用的例子:
一般咱們會在自定義的Application中來記住app中建立的Activity,從而中途在某個Activity中須要徹底退出app時能夠徹底的銷燬全部已經打開的Activity,這裏咱們能夠對自定義Application改造,讓其只有一個對Activity的弱引用的HashMap,大體的代碼以下:
public class CustomApplication extends Application { private HashMap> activityList = new HashMap>(); private static CustomApplication instance; public static CustomApplication getInstance() { return instance; } public void addActivity(Activity activity) { if (null != activity) { L.d("********* add Activity " + activity.getClass().getName()); activityList.put(activity.getClass().getName(), new WeakReference<>(activity)); } } public void removeActivity(Activity activity) { if (null != activity) { L.d("********* remove Activity " + activity.getClass().getName()); activityList.remove(activity.getClass().getName()); } } public void exit() { for (String key : activityList.keySet()) { WeakReference activity = activityList.get(key); if (activity != null && activity.get() != null) { L.d("********* Exit " + activity.get().getClass().getSimpleName()); activity.get().finish(); } } System.exit(0); android.os.Process.killProcess(android.os.Process.myPid()); } }
咱們在自定義的Activity的基類BaseActivity中的onCreate執行:
CustomApplication.getInstance().addActivity(this);
在BaseActivity的onDestroy中執行:
CustomApplication.getInstance().removeActivity(this);
這樣子自定義Application就能不泄露的持有全部打開的Activity的引用,同時當你須要中途退出app的時候,只須要執行:
//完美退出程序方法 CustomApplication.getInstance().exit();
哪些狀況會致使內存泄漏
到此你應該對內存泄漏的本質已經有所瞭解了,這裏列舉出一些會致使內存泄漏的地方,能夠做爲排查內存泄漏的一個checklist
使用leakcanary
以前Android開發一般使用MAT內存分析工具來排查heap的問題,之類的文章比較多,你們能夠本身找。這裏推薦一個叫作leakcanary的工具,他能夠集成在你的代碼裏面。這個東西你們能夠參考:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0509/2854.html