Android內存泄露OOM異常處理優(yōu)化

內存泄漏介紹

1.什么是OOM?

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

2.OOM導致的現(xiàn)象?

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

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

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

3.什么是內存抖動

內存泄漏發(fā)生時的主要表現(xiàn)為內存抖動瓦堵,可用內存慢慢變少,在Android studio中可以通過Android Profiler工具查看內存抖動情況

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

內存抖動一般是在循環(huán)語句中創(chuàng)建臨時對象或在繪制時配置大量對象導致泼各。 內存抖動會帶來UI的卡頓,因為大量的對象創(chuàng)建履肃,會很快消耗剩余內存仔沿,導致GC回收,GC會占用大量的幀繪制時間榆浓,從而導致UI卡頓

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

1. 創(chuàng)建的資源沒有及時釋放:

如何避免:

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

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

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

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

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

如何避免:

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

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

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

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

如何避免:

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

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

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

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

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

如何避免:

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

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

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

常見的內存泄漏案例分析

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

由于單例模式的靜態(tài)特性抖坪,使得它的生命周期和我們的應用一樣長萍鲸,如果讓單例無限制的持有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引發(fā)的內存泄漏

內存泄漏代碼片段:
當Activity退出時,延時任務Message還在主線程的MessageQueue中等待,此時的Message持有Handler的強引用,并且Handler是Activity類的非靜態(tài)內部類,所以也默認持有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.使用靜態(tài)內部類,通過弱引用傳入外部類的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.內部類引起的內存泄漏

內部類默認持有外部類強引用,容易出現(xiàn)內存泄漏

內存泄漏代碼片段:

    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.把線程類聲明為靜態(tài)的類,如果要用到Activity對象,那么就作為參數傳入且為WeakReference 2.在Activity的onDestroy時,停止線程的執(zhí)行

 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性能優(yōu)化之 ---- 布局優(yōu)化(一)

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

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

Android APP性能優(yōu)化之 ---- 優(yōu)化監(jiān)測工具(四)

Android APP性能優(yōu)化之 ---- APK瘦身 App啟動優(yōu)化

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市擦俐,隨后出現(xiàn)的幾起案子脊阴,更是在濱河造成了極大的恐慌,老刑警劉巖蚯瞧,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘿期,死亡現(xiàn)場離奇詭異,居然都是意外死亡埋合,警方通過查閱死者的電腦和手機备徐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甚颂,“玉大人蜜猾,你說我怎么就攤上這事秀菱。” “怎么了蹭睡?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵衍菱,是天一觀的道長。 經常有香客問我肩豁,道長脊串,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任清钥,我火速辦了婚禮琼锋,結果婚禮上,老公的妹妹穿的比我還像新娘循捺。我一直安慰自己斩例,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布从橘。 她就那樣靜靜地躺著念赶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恰力。 梳的紋絲不亂的頭發(fā)上叉谜,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音踩萎,去河邊找鬼停局。 笑死,一個胖子當著我的面吹牛香府,可吹牛的內容都是我干的董栽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼企孩,長吁一口氣:“原來是場噩夢啊……” “哼锭碳!你這毒婦竟也來了?” 一聲冷哼從身側響起勿璃,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤擒抛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后补疑,有當地人在樹林里發(fā)現(xiàn)了一具尸體歧沪,經...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年莲组,在試婚紗的時候發(fā)現(xiàn)自己被綠了诊胞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡胁编,死狀恐怖厢钧,靈堂內的尸體忽然破棺而出鳞尔,到底是詐尸還是另有隱情,我是刑警寧澤早直,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布寥假,位于F島的核電站,受9級特大地震影響霞扬,放射性物質發(fā)生泄漏糕韧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一喻圃、第九天 我趴在偏房一處隱蔽的房頂上張望萤彩。 院中可真熱鬧,春花似錦斧拍、人聲如沸雀扶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愚墓。三九已至,卻和暖如春昂勉,著一層夾襖步出監(jiān)牢的瞬間浪册,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工岗照, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留村象,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓攒至,卻偏偏與公主長得像厚者,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子迫吐,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360