Android內存泄露OOM異常處理優化

內存泄漏介紹

1.什么是OOM?

OOM(out of memory)即內存溢出.在程序中,對內存使用超過一定的閥值就會導致內存溢出. 而當一個對象已經不需要在使用了,本該被回收,而另一個正在使用的對象持有它的引用,導致該對象不能被回收就產生了內存泄漏.內存泄露太多導致無法繼續申請內存是導致OOM的主要原因之一.

2.OOM導致的現象?

1.程序卡頓,響應速度慢(內存占用高時JVM虛擬機會頻繁觸發GC)

2.由于APP運行內存限制,會導致直接崩潰(OutOfMemoryError)

3.觸發Low Memory Killer機制,應用莫名被殺

3.什么是內存抖動

內存泄漏發生時的主要表現為內存抖動,可用內存慢慢變少,在Android studio中可以通過Android Profiler工具查看內存抖動情況

堆內存都有一定的大小,能容納的數據是有限制的,當Java堆的大小太大時,垃圾收集會啟動停止堆中不再應用的對象,來釋放內存。當在極短時間內分配給對象和回收對象的過程就是內存抖動。

內存抖動一般是在循環語句中創建臨時對象或在繪制時配置大量對象導致。 內存抖動會帶來UI的卡頓,因為大量的對象創建,會很快消耗剩余內存,導致GC回收,GC會占用大量的幀繪制時間,從而導致UI卡頓

導致內存泄漏(溢出)的原因有哪些?

1. 創建的資源沒有及時釋放:

如何避免:

  • 資源性對象及時關閉,使用的任何資源都要及時關閉或者異常處理,保證在最惡劣的情況下資源可以得到釋放 (如:Cursor、File、Receiver、Sensor)

  • 資源的注冊和反注冊成對出現(廣播,觀察者) 如事件注冊后未注銷,會導致觀察者列表中持有Context對象的引用

  • 頁面退出時及時清理一些資源占用(集合對象,WebView) 容器中的對象在不用的時候及時清理,WebView存在著內存泄漏的問題,在應用中只要使用一次,WebView,內存就不會被釋放掉.

  • 資源重復利用(使用adapter的時候使用convertView)

2. 保存了耗用內存過大的對象(Bitmap)

如何避免:

  • Bitmap沒有使用的時候及時recycle釋放內存

  • 對大圖進行壓縮,使用軟引用或弱引用(使用這兩種引用代碼需要做不為空判斷)

  • 使用緩存技術(LruCache和DiskLruCache)

3.Static引用的資源 消耗過多的實例(Context的使用)

如何避免:

  • 盡量避免static成員變量引用資源 消耗過多的實例,比如Context;靜態變量不要持有大數據對象

  • 使用軟引用代替強引用**

  • 盡量使用ApplicationContext,因為Application的Context的生命周期比較長,引用它不會出現內存泄露的問題

  • 對于內部類盡量使用靜態內部類,避免由于內部類導致的內存泄漏(靜態內部類可以通過軟引用使用外部的Context)如:Handler使用靜態內部類

4.線程生命周期不可控導致內存泄漏

如何避免:

將線程的內部類,改為靜態內部類,在線程內部采用弱引用保存Context引用

線程優化:避免程序中存在大量的Thread.可以使用線程池,并且頁面退出時,終止線程.**

    例: 
    在activity中handler發一個延時任務,activity退出后,延遲任務的message還在主線程,它持有activity的handler引用,所以造成內存泄漏(handler非靜態類,它會持有外部類的引用,也就是activity);
    這里可以把handler聲明為static的,則handler的存活和activity生命周期無關了,如果handler內部使用外部類的非static對象(如:Context),應該通過弱引用傳入,activity銷毀時,移除looper線程中的消息.

常見的內存泄漏案例分析

1、單例模式引起的內存泄露

由于單例模式的靜態特性,使得它的生命周期和我們的應用一樣長,如果讓單例無限制的持有Activity的強引用就會導致內存泄漏

內存泄漏代碼片段:

public class MyInstance {
    private static MyInstance mMyInstance;
    private Context mContext;

    private MyInstance(Context context) {
        this.mContext = context;
    }
    public static MyInstance getInstance(Context context) {
        if (mMyInstance == null) {
            synchronized (MyInstance.class) {
                if (mMyInstance == null) {
                    mMyInstance = new MyInstance(context);
                }
            }
        }
        return mMyInstance;
    }

    private View mView = null;
    public void setXXView(View xxView) {
        mView = xxView;
    }
}

解決方案:

  1. 傳入的Context使用ApplicationContext;
  2. 將該屬性的引用方式改為弱引用;
public class MyInstance {
    private static MyInstance mMyInstance;
    private Context mContext;

    private MyInstance(Context context) {
        this.mContext = context.getApplicationContext();
    }

    public static MyInstance getInstance(Context context) {
        if (mMyInstance == null) {
            synchronized (MyInstance.class) {
                if (mMyInstance == null) {
                    mMyInstance = new MyInstance(context);
                }
            }
        }
        return mMyInstance;
    }

    private WeakReference<View> mView = null;
    public void setXXView(View xxView) {
        mView = new WeakReference<View>(xxView);
    }
}

2.Handler引發的內存泄漏

內存泄漏代碼片段:
當Activity退出時,延時任務Message還在主線程的MessageQueue中等待,此時的Message持有Handler的強引用,并且Handler是Activity類的非靜態內部類,所以也默認持有Activity的強引用.

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(), 5000);
    }

解決方案: 1.使用靜態內部類,通過弱引用傳入外部類的Context 2.在onDestroy中調用mHandler.removeCallbacksAndMessages(null)

    private final Handler mHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(), 5000);
    }

    static class MyHandler extends Handler {
        private SoftReference<Activity> reference;

        public MyHandler(Activity activity) {
            // 持有 Activity 的軟引用
            reference = new SoftReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity = reference.get();
            if (activity != null && !activity.isFinishing()) {
                switch (msg.what) {
                    // 處理消息
                }
            }
        }
    }

4.內部類引起的內存泄漏

內部類默認持有外部類強引用,容易出現內存泄漏

內存泄漏代碼片段:

    public void createNonStaticInnerClass(){
        CustomThread mCustomThread = new CustomThread();
        mCustomThread.start();
    }
    public class CustomThread extends Thread{
        @Override
        public void run() {
            super.run();
            while (true){
                try {
                    Thread.sleep(5000);
                    Log.i(TAG,"CustomThread ------- 打印");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

解決方案: 1.把線程類聲明為靜態的類,如果要用到Activity對象,那么就作為參數傳入且為WeakReference 2.在Activity的onDestroy時,停止線程的執行

 public static class CustomThread extends Thread{
        private WeakReference<MainActivity> mActivity;
        public CustomThread(MainActivity activity){
            mActivity = new WeakReference<MainActivity>(activity)
        }
    }

5.Activity Context 的不正確使用引起的內存泄漏

使用ApplicationContext代替Activity Context ,因為ApplicationContext的生命周期也就是該應用生命周期,不依賴于activity的生命周期

內存泄露的工具?

1. MAT工具(很全面,但入手較難,MAT為Eclipse自帶工具)
2. Android Profiler(圖像化工具,AndroidStudio自帶工具)
3. LeakCanary工具(簡便)
    源碼:https://github.com/square/leakcanary
     支持Eclipse的庫:http://download.csdn.net/detail/ytuglt/9533490

相關鏈接直達:

Android APP性能優化之 ---- 布局優化(一)

Android APP性能優化之 ---- 內存優化(二)

Android APP性能優化之 ---- 代碼優化(三)

Android APP性能優化之 ---- 優化監測工具(四)

Android APP性能優化之 ---- APK瘦身 App啟動優化

Android內存泄露OOM的原因及解決方法

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。