內(nèi)存泄漏——筆記1

此篇主要記錄了兩點:
1、內(nèi)存泄漏與內(nèi)存溢出的區(qū)別
2狭握、引起內(nèi)存泄漏的幾種情況
3、和內(nèi)存相關的一些知識點記錄疯溺,這部分另寫筆記论颅。

一、技術點

1囱嫩、內(nèi)存泄漏與內(nèi)存溢出的區(qū)別

  • 內(nèi)存泄漏:指程序在申請內(nèi)存后恃疯,被某個對象一直持有,導致無法釋放已申請的內(nèi)存空間挠说。一次內(nèi)存泄漏的危害可以忽略澡谭,但內(nèi)存泄漏堆積后果很嚴重,無論多少內(nèi)存损俭,遲早會被占光。
  • 內(nèi)存溢出:指程序在申請內(nèi)存時潘酗,沒有足夠的內(nèi)存空間供其使用杆兵,出現(xiàn)out of memory。Android系統(tǒng)為每個應用程序申請到的內(nèi)存有限仔夺,一般為64M或者128M等琐脏,我們可以在清單文件中進行配置,android:largeheap = "true" 從而給APP申請更大的內(nèi)存空間缸兔。
JVM與Android關系

虛擬機:JVM的作用是把平臺無關的.class里面的字節(jié)碼翻譯成平臺相關的機器碼日裙,來實現(xiàn)跨平臺。Dalvik和Art時安卓中使用的虛擬機惰蜜。
注:本地方法棧昂拂,屬于native層,暫不需要管抛猖,它和Java層是不一樣的垃圾回收機制格侯。

內(nèi)存溢出會發(fā)生在堆內(nèi)存和虛擬機棧:

  • 堆內(nèi)存溢出
    例1:


    堆內(nèi)存溢出示例1

    例2:如生產(chǎn)者與消費者模型鼻听,如注冊回調(diào),忘記注銷联四。添加到隊列撑碴,忘記控制隊列大小。
    例3:fastjson解析(間接)循環(huán)引用朝墩。(這里例子還不是很理解醉拓,沒有遇到過)

  • 棧(虛擬棧)內(nèi)存溢出
    方法遞歸。

內(nèi)存泄漏會發(fā)生在方法區(qū)收苏、堆內(nèi)存和虛擬機棧亿卤。
具體情況見Q3。

2倒戏、垃圾回收機制的原理是什么

3怠噪、詳細說說什么情況下會出現(xiàn)Android內(nèi)存泄漏(六大類)

(1)單例使用不當(生命周期不一樣)

??說明:單例的靜態(tài)特性使得它的生命周期同應用的生命周期一樣長,如果一個對象已經(jīng)沒有用處了杜跷,但是單例還持有它的引用傍念,那么在整個應用程序的生命周期它都不能正常被回收,從而導致內(nèi)存泄漏葛闷。

public class AppSetting {
    private static AppSetting mInstance;
    private Context mContext;

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

    public static AppSetting getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new AppSetting(context);
        }
        return mInstance;
    }
}

//使用
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AppSetting.getInstance(this);
    }
}

解決:
將AppSetting修改如下:

public class AppSetting {
    private static AppSetting mInstance;
    private Context mContext;

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

    public static AppSetting getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new AppSetting(context);
        }
        return mInstance;
    }
}
(2)靜態(tài)變量導致內(nèi)存泄漏

靜態(tài)變量存儲在方法區(qū)憋槐,它的生命周期從類加載開始,到整個進程結束淑趾。一旦靜態(tài)變量初始化后阳仔,它所持有的引用只有等到進程結束才會釋放。

public class MainActivity extends AppCompatActivity {
    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sInfo = new Info(this);
    }
}

class Info {
    Activity mActivity;

    public Info(Activity activity) {
        mActivity = activity;
    }
}

分析:
Info作為Activity的靜態(tài)成員扣泊,并且持有Activity的引用近范,但是sInfo作為靜態(tài)變量,生命周期肯定比Activity長延蟹。所有當Activity退出后评矩,sInfo仍然引用了Activity,導致Activity不能被回收阱飘,引起內(nèi)存泄漏斥杜。
解決:
在Activity退出時,可以在onDestory中沥匈,把靜態(tài)引用變量置為null蔗喂。

@Override
    protected void onDestroy() {
        super.onDestroy();
        if (sInfo != null) {
            sInfo = null;
        }
    }
(3)非靜態(tài)內(nèi)部類導致內(nèi)存泄漏

非靜態(tài)內(nèi)部類(包括匿名內(nèi)部類)默認就會持有外部類的引用,當非靜態(tài)內(nèi)部類對象的生命周期比外部類對象的生命周期長時高帖,就會導致內(nèi)存泄漏缰儿。常見于Handler、Thread棋恼、AsyncTask返弹。
例1:Handler

public class MainActivity1 extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {

            }
        }
    };
}

分析:
在此例中锈玉,mHandler會默認持有外部類(MainActivity1)的引用,導致MainActivity1不能被回收义起,引起內(nèi)存泄漏拉背。
解決:
通過弱引用解決。

public class MainActivity1 extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new MyHandler(this);

    private static class MyHandler extends Handler {
        private WeakReference<MainActivity1> mActivity1WeakReference;

        public MyHandler(MainActivity1 activity1) {
            mActivity1WeakReference = new WeakReference<>(activity1);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            MainActivity1 activity1 = mActivity1WeakReference.get();
            switch (msg.what) {
                //處理邏輯
            }
        }
    }

}

例2:Thread

public class MainActivity1 extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    //處理任務
                    SystemClock.sleep(1000L);
                }
            }
        }).start();
    }
}

分析:
1默终、內(nèi)部匿名的Thread實例會長久運行椅棺,不會被系統(tǒng)GC回收。
2齐蔽、非靜態(tài)內(nèi)部類會持有外部類的引用两疚。
在此例中,即使MainActivity1退出了含滴,但是始終有一個thread持有它的引用诱渤,導致MainActivity1不能被回收,引起內(nèi)存泄漏谈况。
解決:
1勺美、加上static后,內(nèi)部類就不會持有MainActivity1的隱式引用了碑韵。

public class MainActivity1 extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyThread().start();
    }

    private static class MyThread extends Thread {
        @Override
        public void run() {
            while (true) {
                //處理任務
                SystemClock.sleep(1000L);
            }
        }
    }
}

2赡茸、解決Thread無法回收問題

public class MainActivity1 extends AppCompatActivity {
    private MyThread mMyThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mMyThread = new MyThread();
        mMyThread.start();
    }

    private static class MyThread extends Thread {
        private boolean mIsRunning = false;

        @Override
        public void run() {
            mIsRunning = true;
            while (mIsRunning) {
                //處理任務
                SystemClock.sleep(1000L);
            }
        }

        public void close() {
            mIsRunning = false;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mMyThread.close();
    }
}

分析:
給Thread添加結束的標志位。當thread任務執(zhí)行完成后祝闻,Java會幫我們把回收線程占卧。因此,我們要養(yǎng)成為thread設置退出邏輯的習慣联喘,保證thread可以運行結束华蜒。
注:
Java threads會一直存在,只有當線程運行完成或被殺死掉豁遭,線程才會被回收友多。

(4)未取消注冊或回調(diào)導致內(nèi)存泄漏

我們在Activity中注冊廣播后,如果在Activity銷毀后不取消注冊堤框,那么這個廣播會一直存在,同上面所說的非靜態(tài)內(nèi)部類一樣持有Activity引用纵柿,導致內(nèi)存泄漏。因此注冊廣播后一定要在Activity銷毀后取消注冊。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.registerReceiver(mReceiver, new IntentFilter());
    }

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            //收到廣播時的處理
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unregisterReceiver(mReceiver);
    }
}
(5)資源未關閉或釋放導致內(nèi)存泄漏

在使用流等資源時要及時關閉爱谁,這些資源在進行讀寫時通常都使用了緩沖瓶蝴,如果不及時關閉,這些緩存對象就會一直被占用渊跋,引起內(nèi)存泄漏腊嗡。

4着倾、什么是內(nèi)存抖動,產(chǎn)生的本質(zhì)是什么燕少?(年輕堆與老年堆機制)

5卡者、你是怎么處理crash異常的,對于不能定位行數(shù)的問題怎么解決

6客们、你是怎么對內(nèi)存進行管理的崇决?(Bitmap,對象)

二底挫、相關知識

1恒傻、內(nèi)存詳解
2、垃圾回收機制
3建邓、GcRoot原理詳解

參考:
1盈厘、面試官:內(nèi)存泄漏連環(huán)問。被問懵了官边?來看看這部視頻_嗶哩嗶哩 (゜-゜)つロ 干杯~-bilibili
2沸手、內(nèi)存泄露:Thread是如何造成內(nèi)存泄露的 - 簡書 (jianshu.com)

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拒逮,隨后出現(xiàn)的幾起案子罐氨,更是在濱河造成了極大的恐慌,老刑警劉巖滩援,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件栅隐,死亡現(xiàn)場離奇詭異,居然都是意外死亡玩徊,警方通過查閱死者的電腦和手機租悄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恩袱,“玉大人泣棋,你說我怎么就攤上這事∨纤” “怎么了潭辈?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長澈吨。 經(jīng)常有香客問我把敢,道長,這世上最難降的妖魔是什么谅辣? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任修赞,我火速辦了婚禮,結果婚禮上桑阶,老公的妹妹穿的比我還像新娘柏副。我一直安慰自己勾邦,他們只是感情好,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布割择。 她就那樣靜靜地躺著眷篇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锨推。 梳的紋絲不亂的頭發(fā)上铅歼,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天,我揣著相機與錄音换可,去河邊找鬼椎椰。 笑死,一個胖子當著我的面吹牛沾鳄,可吹牛的內(nèi)容都是我干的慨飘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼译荞,長吁一口氣:“原來是場噩夢啊……” “哼瓤的!你這毒婦竟也來了?” 一聲冷哼從身側響起吞歼,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤圈膏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后篙骡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稽坤,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年糯俗,在試婚紗的時候發(fā)現(xiàn)自己被綠了尿褪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡得湘,死狀恐怖杖玲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淘正,我是刑警寧澤摆马,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站鸿吆,受9級特大地震影響今膊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伞剑,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望市埋。 院中可真熱鬧黎泣,春花似錦恕刘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至托呕,卻和暖如春含蓉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背项郊。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工馅扣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人着降。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓差油,卻偏偏與公主長得像,于是被迫代替她去往敵國和親任洞。 傳聞我的和親對象是個殘疾皇子蓄喇,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容