Android常見記憶體洩漏總結

ljwx9527發表於2019-02-14

記憶體洩漏原理
真的很詳細, 獲益匪淺

部落格原文
Java記憶體洩漏的根本原因是什麼呢?長生命週期的物件持有短生命週期物件的引用就很可能發生記憶體洩漏,儘管短生命週期物件已經不再需要,但是因為長生命週期持有它的引用而導致不能被回收,這就是Java中記憶體洩漏的發生場景。

知識點:
1, new的物件成員變數中, 這部分記憶體在不使用時由GC來回收
2, 方法體的區域性變數中,方法執行完,則區域性變數持有的記憶體會被自動釋放,也就是不再持有物件的引用
3, 當一個物件的任務已經執行完畢,正常變數名的引用已經結束, 但是其他還在執行的物件, 還間接持有該物件, 導致記憶體洩漏
4, 判斷物件是否可達

avatar

不可達

何為不可達
(1)改變物件的引用,如置為null或者指向其他物件。
Object x=new Object();//object1
Object y=new Object();//object2
x=y;//object1 變為垃圾
x=y=null;//object2 變為垃圾
(2)超出作用域
if(i==0){
Object x=new Object();//object1
}
//括號結束後object1將無法被引用,變為垃圾
(3)類巢狀導致未完全釋放 class A{ A a; } A x= new A();//分配一個空間 x.a= new A();//又分配了一個空間 x=null;//將會產生兩個垃圾
(4)執行緒中的垃圾 class A implements Runnable{
void run(){ //.... }
}
//main
A x=new A();//object1
x.start();
x=null;
//等執行緒執行完後object1才被認定為垃圾
這樣看,確實在程式碼執行過程中會產生很多垃圾,不過不用擔心,java可以有效地處理他們。

5, 記憶體洩漏示意圖

avatar

1, 非靜態內部類

class Activity{
    r = new Runnable(){
        run(){//耗時操作}
    }
    t = new Thread(){}
}
複製程式碼

當退出activity, 但耗時操作還未結束時, 就會記憶體洩漏, 因為new介面在java語法裡, 就是建立匿名內部類的意思, java語法裡介面是不能new的, 而非靜態內部類會持有外部類, 所以Activity類雖然已經結束, 理論應該被GC回收, 但是還在被Runnable持有, 導致記憶體洩漏

解決方法:

class Activity{
    MyRunnable r = new MyRunnable()
    void create()
    private static class MyThread extends Thread {
    }
    private static class MyRunnable implements Runnable{
    }
}
複製程式碼

改成靜態內部類, 因為靜態內部類, 並不在靜態區, 建立出來後就是一個普通的物件, 生命週期跟隨普通類

2, Context被靜態持有

calss Util{
    private static Context context;
    public static void utils(Context context){
        this.context = context;
    }
}
複製程式碼

因為context被靜態持有後, 生命週期太長, 當context的Activity已經退出時, 應該被回收, 但依然被Util持有, 導致Activity物件的記憶體無法回收,

解決辦法:
1,用Application的context, 類似於Dialog這種必須要Activity的Context的, 可以使用其他方法,
2,不要將context傳給靜態變數,也不要傳給其他生命週期可能會長於Activity的物件

3,Handler 這篇文章解釋的很好

其實用了RxJava之後, 現在Handler用的比較少了, 我們用Handler就是因為執行緒切換, 既然有其他執行緒, 那其他執行緒的生命週期就可能比Activity長, 當Activity結束後, Handler還在執行, 就會記憶體洩漏

calss Activity{
    Handler mHandler = new Handler(){
        handlerMessage(Message msg){//響應}
    }
    onCreate(){
        mHandler.sendEmptyMessageDelayed(0, 1000 * 2);//延時
    }
}
複製程式碼

解決辦法:
1, 引入軟引用
  強引用的物件, 就絕不收回,
  軟引用的物件,是能不收回就不收回,這裡的能不收回就是指記憶體足夠的情況,
  弱引用的物件,是發現就收回,但是一般情況下不會發現

private static class NoLeakHandler extends Handler{
        private WeakReference<NoLeakActivity> mActivity;

        public NoLeakHandler(NoLeakActivity activity){
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
複製程式碼

每次使用前注意mActivity的判空

2, 及時解除關係,這樣也能防止洩露, 因為handler已經不在持有Activity

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}
複製程式碼

4, 靜態集合儲存物件

Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
    Object o = new Object();
    v.add(o);
    o = null;
}
複製程式碼

Vector的生命週期跟隨整個應用, 雖然變數名o不再持有物件, 但v一直持有, 導致new出來的物件無法回收, 導致記憶體洩漏

解決辦法 將v置為null

總結

  Bitmap 沒呼叫 recycle()方法,對於 Bitmap 物件在不使用時,我們應該先呼叫 recycle() 釋放記憶體,然後才它設定為 null. 因為載入 Bitmap 物件的記憶體空間,一部分是 java 的,一部分 C 的(因為 Bitmap 分配的底層是通過 JNI 呼叫的 )。 而這個 recyle() 就是針對 C 部分的記憶體釋放
  對 Activity 等元件的引用應該控制在 Activity 的生命週期之內; 如果不能就考慮使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部長生命週期的物件引用而洩露
  對於生命週期比Activity長的內部類物件,並且內部類中使用了外部類的成員變數,可以這樣做避免記憶體洩漏

將內部類改為靜態內部類
靜態內部類中使用弱引用來引用外部類的成員變數

另外一篇講原理
這個作者我很喜歡

一個不錯的評論
1.JVM 的記憶體模型和 Android 虛擬機器是有一點區別,JVM 基於棧,但 Android 虛擬機器基於暫存器,所以 Android 虛擬機器棧中不存在區域性變數表這種說法,裡頭其實是一組虛擬暫存器。
2.博主拿物件的引用在棧上的情況來解釋記憶體洩露是不恰當的,因為棧上引用的物件是不會導致記憶體洩露的,當一個方法執行完的時候,物件引用自然就置空了。 只有在成員變數上引用的物件會導致記憶體洩露,因為引用是分配在堆上的。

相關文章