名詞解釋
內存泄漏:即memory leak。是指內存空間使用完畢后無法被釋放的現象,雖然Java有垃圾回收機制(GC),但是對于還保持著引用, 該內存不能再被分配使用,邏輯上卻已經不會再用到的對象,垃圾回收器不會回收它們。
內存溢出:即out of memory, 當你要求分配的內存超過了系統給你的內存時, 系統就會拋出out of memory的異常(每個Android能用的內存是有限的) 。比如: 當前應用只剩下4M的空間可用, 但你卻加載得到一個需要占用5M空間的圖片Bitmap對象, 就會拋出溢出的異常
常見內存泄漏場景&解決方案
1.非靜態內部類、匿名類()
非靜態內部類會持有外部類的引用,如果非靜態內部類的實例是靜態的,就會長期的維持著外部類的引用,阻止被系統回收。
解決方案是使用靜態內部類
1.1非靜態內部類
非靜態內部類(non static inner class)和 靜態內部類(static inner class)之間的區別。
如果非靜態內部類所創建的實例是靜態的,其生命周期等于應用的生命周期。非靜態內部類默認持有外部類的引用而導致外部類無法釋放,最終造成內存泄露。即外部類中持有非靜態內部類的靜態對象。?
public class MainActivity extends Activity {//非靜態內部類的靜態實例引用public static TestClass testClass = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//保證非靜態內部類的實例只有1個if (testClass == null) {testClass = new TestClass();}}// 非靜態內部類private class TestClass {//todo something}
}
當 MainActivity 銷毀時,因非靜態內部類單例的引用,testClass 的生命周期等于應用的生命周期,持有外部類 MainActivity 的引用,故 MainActivity 無法被 GC 回收,從而導致內存泄漏。
解決方案:
將非靜態內部類設置為:靜態內部類(靜態內部類默認不持有外部類的引用)
該內部類抽取出來封裝成一個單例
盡量避免非靜態內部類所創建的實例是靜態的
1.2 多線程相關的匿名內部類和非靜態內部類(繼承 Thread 類、實現 Runnable 接口、AsyncTask)
當子線程正在處理任務時,如果外部類銷毀, 由于子線程實例持有外部類引用,將使得外部類無法被垃圾回收器(GC)回收,從而造成內存泄露。?
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);new MyThread().start();}private class MyThread extends Thread {@Overridepublic void run() {//todo someting}}
}
解決方案:
- 使用靜態內部類的方式,靜態內部類不默認持有外部類的引用。
-
private static class MyThread extends Thread {@Overridepublic void run() {//todo someting}}
- 當外部類結束生命周期時(即Activity或Fragment),強制結束線程(onDestroy或onDestroyView)。使得工作線程實例的生命周期與外部類的生命周期同步。
@Overrideprotected void onDestroy() {super.onDestroy();myThread.interrupt();}
2. Handler內存泄漏(重新理解為什么 Handler 可能導致內存泄露?)
Handler內部message是被存儲在MessageQueue中的,MessageQueue中的 Message 持有 Handler 實例的引用,有些message不能馬上被處理,存在的時間會很長,導致handler無法被回收,如果handler是非靜態的(內部類、匿名內部類),默認持有外部類的引用(如 MainActivity 實例),導致它的外部類無法被回收。
?
public class MainActivity extends Activity {private MyHandler myHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);myHandler = new MyHandler();new Thread() {@Overridepublic void run() {try {//執行耗時操作Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}//發送消息myHandler.sendEmptyMessage(1);}}.start();}private class MyHandler extends Handler {@Overridepublic void handleMessage(Message msg) {//處理消息事件}}
}
解決方案:
- 使用靜態內部類+弱引用的方式,保證外部類能被回收。因為弱引用的對象擁有短暫的生命周期,在垃圾回收器線程掃描時,一旦發現了具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。
public class MainActivity extends Activity {private MyHandler myHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);myHandler = new MyHandler(this);new Thread() {@Overridepublic void run() {try {//執行耗時操作Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}//發送消息myHandler.sendEmptyMessage(1);}}.start();}public void test() {Log.d("TAG", "test");}private static class MyHandler extends Handler {//定義弱引用實例private WeakReference<Activity> reference;//在構造方法中傳入需持有的Activity實例public MyHandler(Activity activity) {//使用 WeakReference 弱引用持有 Activity 實例reference = new WeakReference<Activity>(activity);}@Overridepublic void handleMessage(Message msg) {//處理消息事件//調用Activity實例中的方法((MainActivity) reference.get()).test();}}
}
- 當外部類結束生命周期時,清空 Handler 內消息隊列。
@Overrideprotected void onDestroy() {if (myHandler!= null) {myHandler.removeCallbacksAndMessages(null);}super.onDestroy();}
3. Context導致內存泄漏
根據場景確定使用Activity的Context還是Application的Context,因為二者生命周期不同,對于不必須使用Activity的Context的場景(Dialog),一律采用Application的Context,單例模式是最常見的發生此泄漏的場景,比如傳入一個Activity的Context被靜態類引用,導致無法回收
4. 靜態View導致泄漏
使用靜態View可以避免每次啟動Activity都去讀取并渲染View,但是靜態View會持有Activity的引用,導致無法回收。
解決方案:
盡量避免 static 成員變量引用資源耗費過多的實例(如 Context),若需引用 Context,則盡量使用Applicaiton的 Context。
使用弱引用(WeakReference) 代替強引用持有實例。
在Activity銷毀的時候將靜態View設置為null
5.資源對象未關閉導致
對于資源若在 Activity 銷毀時無及時關閉 / 注銷這些資源,則這些資源將不會被回收,從而造成內存泄漏。
如廣播、文件、Bitmap、數據庫等使用?
//對于廣播BroadcastReceiver:注銷注冊
unregisterReceiver(broadcastReceiver);//對于文件流File:關閉流
inputStream / outputStream.close();//對于數據庫游標cursor:使用后關閉游標
cursor.close();//對于圖片資源Bitmap:Android分配給圖片的內存只有8M,若1個Bitmap對象占內存較多,當它不再被使用時,應調用recycle()回收此對象的像素所占用的內存;最后再賦為null
bitmap.recycle();
bitmap = null;// 對于動畫(屬性動畫),將動畫設置成無限循環播放setRepeatCount(ValueAnimator.INFINITE);后
// 在Activity退出時記得停止動畫
animator.cancel();
6.監聽器未關閉
很多需要register和unregister的系統服務要在合適的時候進行unregister,手動添加的listener也需要及時移除
7.集合中的對象未清理
集合用于保存對象,如果集合越來越大,不進行合理的清理,
8. WebView導致的內存泄漏(目前沒有遇到)
WebView只要使用一次,內存就不會被釋放,所以WebView都存在內存泄漏的問題。
通常的解決辦法是為WebView單開一個進程,使用AIDL進行通信,根據業務需求在合適的時機釋放掉
內存泄漏分析工具
lint
lint 是一個靜態代碼分析工具,同樣也可以用來檢測部分會出現內存泄露的代碼,平時編程注意 lint 提示的各種黃色警告即可。如:
?
也可以手動檢測,在 Android Studio 中選擇 Code->Inspect Code。
?
然后會彈出選擇檢測范圍
點擊Ok,等待分析結果
?
這個工具除了會檢測內存泄漏,還會檢測代碼是否規范、是否有沒用到的導包、可能的bug、安全問題等等。
?
Memory Profile :Memory Profile 的使用
LeakCanary :LeakCanary