Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 內存洩露簡介、典型情景及檢測解決

Android 內存洩露簡介、典型情景及檢測解決

編輯:關於Android編程

什麼是內存洩露?

Android虛擬機的垃圾回收采用的是根搜索算法。GC會從根節點(GC Roots)開始對heap進行遍歷。到最後,部分沒有直接或者間接引用到GC Roots的就是需要回收的垃圾,會被GC回收掉。內存洩漏指的是進程中某些對象(垃圾對象)已經沒有使用價值了,但是它們卻可以直接或間接地引用到gc roots導致無法被GC回收。無用的對象占據著內存空間,導致不能及時回收這個對象所占用的內存。內存洩露積累超過Dalvik堆大小,就會發生OOM(OutOfMemory)。

內存洩露的經典場景

非靜態內部類的靜態實例

由於內部類默認持有外部類的引用,而靜態實例屬於類。所以,當外部類被銷毀時,內部類仍然持有外部類的引用,致使外部類無法被GC回收。因此造成內存洩露。

舉個栗子

    private static Leak mLeak;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mLeak = new Leak();
    }

    class Leak {
    }

錯誤栗子說明:static關鍵字修飾mLeak屬性,將mLeak存在靜態區中,而Leak為內部類,默認持有外部類的引用。當Activity銷毀時,mLeak緊緊抱住Activity的大腿深情告白:“MLGB!勞資就是不放你走!”。斗不過mLeak屬性的GC,自然不敢回收二手娘們Activity。因此造成內存洩露。

不正確的Handler

錯誤代碼示例:

    private MyHandler mMyHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mMyHandler = new MyHandler();
        mMyHandler.sendMessageDelayed(new Message(), 10 * 1000);
    }

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

正確寫法如下:

    private MyHandler mMyHandler;
    static class MyHandler extends Handler {
        WeakReference mActivityWeak;

        MyHandler(Activity act) {
            mActivityWeak = new WeakReference(act);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mActivityWeak.get() != null) {
                // doSomething
            }
        }
    }

我們知道在handler.sendMessage(msg)時,msg.target會指向handlermsg會插入MessageQueue。此為下面講解的基礎,對這部分不太熟悉的同學可以參考這篇博客。

錯誤之處

MyHandler為內部類,默認持有外部類的引用。當Activity銷毀時,如果MessageQueue中仍有未處理的消息,那麼mMyHandler示例將繼續存在。而mMyHandler持有Activity的引用。故Activity無法被GC回收。

正確解析

static關鍵字修飾MyHandler類,使MyHandler不持有外部類的引用。使用WeakReference保證當
activity銷毀後,不耽誤gc回收activity占用的內存空間,同時在沒被銷毀前,可以引用activity

管它正確錯誤都讓它正確

通過上面的分析,可以得出結論:Handler造成內存洩露時,是因為MessageQueue中還有待處理的Message,那我們在Activity#onDestroy()中移除所有的消息不完事了嘛。反正Activity都銷毀了,MessageQueue中的msg也就什麼存在的意義了,可以移除。代碼如下:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除所有的callback和msg
        mMyHandler.removeCallbacksAndMessages(null);
    }

靜態變量引起內存洩露

這裡以單例模式引起Context洩露為例

public class Singleton {
    private static Singleton instance;
    private Singleton(Context context){
    }

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

錯誤之處

在調用Singleton#getInstance()方法如果傳入了Activity。如果instance沒有釋放,那麼這個Activity將一直存在。因此造成內存洩露。

修正版

new Singleton(context)改為new Singleton(context.getApplicationContext())即可,這樣便和傳入的Activity沒撒關系了。該釋放釋放、該回家回家。

碎碎念

當使用CursorFileSocket等資源時往往都使用了緩沖。在不需要的時候應該及時關閉它們,收回所占的內存空間。 Bitmap不用就recycle掉。注意調用recycle後並不意味著立馬recycle,只是告訴虛擬機:小子,該干活咯! ListView一定要使用ConvertViewViewHolder BraodcastReceiver注冊完事,不用時也要反注冊

內存洩露的檢測

Heap工具

打開DDMS視圖 選中Devices下某個具體的應用程序 選中Devices下第二個小綠點Update Heap 不斷運行程序並點擊Cause GC 關注data Object行、Toal Size列 耍你的APP去吧,如果發現Toal Size越來越大,很可能有內存洩露的發生

MAT(Memory Analyzer Tool)工具

導出.hprof文件

打開DDMS視圖 選中Devices下某個具體的應用程序 選中Devices下第二個小綠點Update Heap 點擊Cause GC 點擊Dump HPROF file 切換到MAT頁卡,默認如下圖所示

default

最顯眼的就是餅圖了,裡面列出了每種類型的數據所占大小。和紅色箭頭所指的Dominator有的一拼,然而這並沒有什麼卵用。我們的重點在Histogram。沒撒說的,點擊它。默認圖如下

defaultvcq906a4w8rH1eK49tH519O1xKGjPC9wPg0KPHA+PGltZyBhbHQ9"currect" src="/uploadfile/Collfiles/20160428/20160428083828217.png" title="\" />

內存洩露Demo

這裡以非靜態內部類的靜態實例為例,Demo只有兩個Activity,MainActivity中只有一個按鈕,點擊跳轉到SecondActivity

public class SecondActivity extends Activity {

    private static Leak mLeak;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mLeak = new Leak();
    }

    class Leak {

    }
}

查找內存洩露

啟動APP,點擊進入SecondActivity,然後按back鍵返回到MainActivity。打開.hprof文件。查找我們的包名com.dyk.memoryleak

first

可以看到,雖然我們結束了SecondActivity,但是SecondActivity仍然存在,內存洩露無疑。

1.右鍵SecondActivity,選擇List Objects—->with incoming references

open

結果如下圖:

result

2.右鍵com.dyk.memoryleak.SecondActivity,選擇Path to GC—->with all references

find

結果如下圖:

get

可以看到是因為mLeak屬性的引用導致SecondActivity無法回收。既然找到了內存洩露的原因,通過上文的介紹,相信改起來難度應該不是很大的。

3.再次進入SecondActivity。由於上次創建的SecondActivity還沒有被回收,可以預期到此時應該存在兩個SecondActivity實例。

yes閱讀到最後~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved