Android性能優(yōu)化-內(nèi)存泄漏

轉(zhuǎn)載地址:http://www.reibang.com/p/402225fce4b2

JVM內(nèi)存管理

Java采用GC進(jìn)行內(nèi)存管理听绳。深入的JVM內(nèi)存管理知識漫贞,推薦《深入理解Java虛擬機(jī)》。關(guān)于內(nèi)存泄漏我們要知道椿每,JVM內(nèi)存分配的幾種策略萧朝。

  • 靜態(tài)的

靜態(tài)的存儲區(qū)略荡,內(nèi)存在程序編譯的時候就已經(jīng)分配好了,這塊內(nèi)存在程序整個運(yùn)行期間都一直存在满哪,它主要存放靜態(tài)數(shù)據(jù)婿斥、全局的static數(shù)據(jù)和一些常量劝篷。

  • 棧式的

在執(zhí)行方法時,方法一些內(nèi)部變量的存儲都可以放在棧上面創(chuàng)建民宿,方法執(zhí)行結(jié)束的時候這些存儲單元就會自動被注釋掉娇妓。棧 內(nèi)存包括分配的運(yùn)算速度很快,因為內(nèi)在在處理器里面活鹰。當(dāng)然容量有限哈恰,并且棧式一塊連續(xù)的內(nèi)存區(qū)域,大小是由操作系統(tǒng)決定的志群,他先進(jìn)后 出着绷,進(jìn)出完成不會產(chǎn)生碎片,運(yùn)行效率高且穩(wěn)定

  • 堆式的

也叫動態(tài)內(nèi)存 锌云。我們通常使用new 來申請分配一個內(nèi)存荠医。這里也是我們討論內(nèi)存泄漏優(yōu)化的關(guān)鍵存儲區(qū)。GC會根據(jù)內(nèi)存的使用情況桑涎,對堆內(nèi)存里的垃圾內(nèi)存進(jìn)行回收彬向。堆內(nèi)存是一塊不連續(xù)的內(nèi)存區(qū)域,如果頻繁地new/remove會造成大量的內(nèi)存碎片石洗,GC頻繁的回收幢泼,導(dǎo)致內(nèi)存抖動,這也會消耗我們應(yīng)用的性能

我們知道可以調(diào)用 System.gc();進(jìn)行內(nèi)存回收讲衫,但是GC不一定會執(zhí)行缕棵。面對GC的機(jī)制,我們是否無能為力涉兽?其實我們可以通過聲明一些引用標(biāo)記來讓GC更好對內(nèi)存進(jìn)行回收招驴。

  • StrongReference (強(qiáng)引用) 任何時候GC是不能回收他的,哪怕內(nèi)存不足時枷畏,系統(tǒng)會直接拋出異常OutOfMemoryError别厘,也不會去回收 進(jìn)程終止
  • SoftReference (軟引用) 當(dāng)內(nèi)存足夠時不會回收這種引用類型的對象,只有當(dāng)內(nèi)存不夠用時才會回收 內(nèi)存不足拥诡,進(jìn)行GC的時候
  • WeakReference (弱引用) GC一運(yùn)行就會把給回收了 GC后終止
  • PhantomReference (虛引用) 如果一個對象與虛引用關(guān)聯(lián)触趴,則跟沒有引用與之關(guān)聯(lián)一樣,在任何時候都可能被垃圾回收器回收 任何時候都有可能
    開發(fā)時渴肉,為了防止內(nèi)存溢出冗懦,處理一些比較占用內(nèi)存并且生命周期長的對象時,可以盡量使用軟引用和弱引用仇祭。

Tip

  • 成員變量全部存儲在堆中(包括基本數(shù)據(jù)類型披蕉,引用及引用的對象實體),因為他們屬于類,類對象最終還是要被new出來的

  • 局部變量的基本數(shù)據(jù)類型和引用存在棧中没讲,應(yīng)用的對象實體存儲在堆中眯娱。因為它們屬于方法當(dāng)中的變量,生命周期會隨著方法一起結(jié)束

內(nèi)存泄漏的定義

當(dāng)一個對象已經(jīng)不需要使用了爬凑,本該被回收時徙缴,而有另外一個正在使用的對象持有它的引用,從而導(dǎo)致了對象不能被GC回收嘁信。這種導(dǎo)致了本該被回收的對象不能被回收而停留在堆內(nèi)存中娜搂,就產(chǎn)生了內(nèi)存泄漏

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

  • 內(nèi)存泄漏(Memory Leak)
    進(jìn)程中某些對象已經(jīng)沒有使用的價值了,但是他們卻還可以直接或間接地被引用到GC Root導(dǎo)致無法回收吱抚。當(dāng)內(nèi)存泄漏過多的時候百宇,再加上應(yīng)用本身占用的內(nèi)存,日積月累最終就會導(dǎo)致內(nèi)存溢出OOM

  • 內(nèi)存溢出(OOM)
    當(dāng) 應(yīng)用的heap資源超過了Dalvik虛擬機(jī)分配的內(nèi)存就會內(nèi)存溢出

內(nèi)存泄漏帶來的影響

  • 應(yīng)用卡頓
    泄漏的內(nèi)存影響了GC的內(nèi)存分配秘豹,過多的內(nèi)存泄漏會影響應(yīng)用的執(zhí)行效率

  • 應(yīng)用異常(OOM)
    過多的內(nèi)存泄漏携御,最終會導(dǎo)致 Dalvik分配的內(nèi)存,出現(xiàn)OOM

Android開發(fā)常見的內(nèi)存泄漏

單例造成的內(nèi)存泄漏

  • 錯誤示例

當(dāng)調(diào)用getInstance時既绕,如果傳入的context是Activity的context啄刹。只要這個單例沒有被釋放,那么這個
Activity也不會被釋放一直到進(jìn)程退出才會釋放凄贩。

public class CommUtil {
    private static CommUtil instance;
    private Context context;
    private CommUtil(Context context){
    this.context = context;
    }

    public static CommUtil getInstance(Context mcontext){
    if(instance == null){
        instance = new CommUtil(mcontext);
    }
    return instance;
    }
  • 解決方案

能使用Application的Context就不要使用Activity的Content誓军,Application的生命周期伴隨著整個進(jìn)程的周期

非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏

  • 錯誤示例
 private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }

    }
    class TestResource {

    }
  • 解決方案

將非靜態(tài)內(nèi)部類修改為靜態(tài)內(nèi)部類。(靜態(tài)內(nèi)部類不會隱式持有外部類)

Handler造成的內(nèi)存泄漏

  • 錯誤示例

mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實例疲扎,所以它持有外部類Activity的引用昵时,我們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那么當(dāng)這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息椒丧,而消息隊列中的Message持有mHandler實例的引用壹甥,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收壶熏,引發(fā)內(nèi)存泄漏句柠。

    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }

    private void loadData() {

        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
  • 解決方案

創(chuàng)建一個靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象使用弱引用棒假,這樣在回收時也可以回收Handler持有的對象溯职,這樣雖然避免了Activity泄漏,不過Looper線程的消息隊列中還是可能會有待處理的消息帽哑,所以我們在Activity的Destroy時或者Stop時應(yīng)該移除消息隊列中的消息

    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }

    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

線程造成的內(nèi)存泄漏

  • 錯誤示例

異步任務(wù)和Runnable都是一個匿名內(nèi)部類谜酒,因此它們對當(dāng)前Activity都有一個隱式引用。如果Activity在銷毀之前祝拯,任務(wù)還未完成甚带, 那么將導(dǎo)致Activity的內(nèi)存資源無法回收,造成內(nèi)存泄漏

    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }
    }.execute();


    new Thread(new Runnable() {
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }).start();
  • 解決方案

使用 靜態(tài)內(nèi)部類佳头,避免了Activity的內(nèi)存資源泄漏鹰贵,當(dāng)然在Activity銷毀時候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask::cancel(),避免任務(wù)在后臺執(zhí)行浪費(fèi)資源

   static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private WeakReference<Context> weakReference;

        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }

        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) {
                //...
            }
        }
    }
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }

    new Thread(new MyRunnable()).start();
    new MyAsyncTask(this).execute();

資源未關(guān)閉造成的內(nèi)存泄漏

  • 錯誤示例

對于使用了BraodcastReceiver康嘉,ContentObserver碉输,F(xiàn)ile,Cursor亭珍,Stream敷钾,Bitmap等資源的使用,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷肄梨,否則這些資源將不會被回收阻荒,造成內(nèi)存泄漏

  • 解決方案

在Activity銷毀時及時關(guān)閉或者注銷

使用了靜態(tài)的Activity和View

  • 錯誤示例
static view; 

    void setStaticView() { 
      view = findViewById(R.id.sv_button); 
    } 

    View svButton = findViewById(R.id.sv_button); 
    svButton.setOnClickListener(new View.OnClickListener() { 
      @Override public void onClick(View v) { 
        setStaticView(); 
        nextActivity(); 
      } 
    }); 


    static Activity activity; 

    void setStaticActivity() { 
      activity = this; 
    } 

    View saButton = findViewById(R.id.sa_button); 
    saButton.setOnClickListener(new View.OnClickListener() { 
      @Override public void onClick(View v) { 
        setStaticActivity(); 
        nextActivity(); 
      } 
    });
  • 解決方案

應(yīng)該及時將靜態(tài)的應(yīng)用 置為null,而且一般不建議將View及Activity設(shè)置為靜態(tài)

注冊了系統(tǒng)的服務(wù)众羡,但onDestory未注銷

  • 錯誤示例
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);

-- 解決方案

//不需要用的時候記得移除監(jiān)聽
 sensorManager.unregisterListener(listener);

不需要用的監(jiān)聽未移除會發(fā)生內(nèi)存泄露

  • 錯誤示例
//add監(jiān)聽侨赡,放到集合里面
    tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
        @Override
        public void onWindowFocusChanged(boolean b) {
            //監(jiān)聽view的加載,view加載出來的時候粱侣,計算他的寬高等羊壹。
        }
    });
  • 解決方案
//計算完后,一定要移除這個監(jiān)聽
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);

//Tip

tv.setOnClickListener();//監(jiān)聽執(zhí)行完回收對象齐婴,不用考慮內(nèi)存泄漏
tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監(jiān)聽油猫,放到集合里面,需要考慮內(nèi)存泄漏
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柠偶,一起剝皮案震驚了整個濱河市情妖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诱担,老刑警劉巖鲫售,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異该肴,居然都是意外死亡情竹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門匀哄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秦效,“玉大人,你說我怎么就攤上這事涎嚼≮逯荩” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵法梯,是天一觀的道長苔货。 經(jīng)常有香客問我犀概,道長,這世上最難降的妖魔是什么夜惭? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任姻灶,我火速辦了婚禮,結(jié)果婚禮上诈茧,老公的妹妹穿的比我還像新娘产喉。我一直安慰自己,他們只是感情好敢会,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布曾沈。 她就那樣靜靜地躺著,像睡著了一般鸥昏。 火紅的嫁衣襯著肌膚如雪塞俱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天吏垮,我揣著相機(jī)與錄音敛腌,去河邊找鬼。 笑死惫皱,一個胖子當(dāng)著我的面吹牛像樊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播旅敷,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼生棍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了媳谁?” 一聲冷哼從身側(cè)響起涂滴,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晴音,沒想到半個月后柔纵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡锤躁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年搁料,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片系羞。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡郭计,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出椒振,到底是詐尸還是另有隱情昭伸,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布澎迎,位于F島的核電站庐杨,受9級特大地震影響选调,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灵份,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一仁堪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧各吨,春花似錦、人聲如沸袁铐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剔桨。三九已至屉更,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洒缀,已是汗流浹背瑰谜。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留树绩,地道東北人萨脑。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像饺饭,于是被迫代替她去往敵國和親渤早。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

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