1. 程式人生 > >全方位帶你徹底搞懂Android記憶體洩露

全方位帶你徹底搞懂Android記憶體洩露



1Java記憶體回收方式

Java判斷物件是否可以回收使用的而是可達性分析演算法。

在主流的商用程式語言中(Java和C#),都是使用可達性分析演算法判斷物件是否存活的。這個演算法的基本思路就是通過一系列名為"GC Roots"的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的,下圖物件object5, object6, object7雖然有互相判斷,但它們到GC Roots是不可達的,所以它們將會判定為是可回收物件。

在Java語言裡,可作為GC Roots物件的包括如下幾種:

a.虛擬機器棧(棧楨中的本地變量表)中的引用的物件

b.方法區中的類靜態屬性引用的物件

c.方法區中的常量引用的物件

d.本地方法棧中JNI的引用的物件 

摘自《深入理解Java虛擬機器》

2使用leakcanary檢測洩漏

關於LeakCanary使用參考以下文章:

LeakCanary: 讓記憶體洩露無所遁形

http://www.liaohuqiu.net/cn/posts/leak-canary/

LeakCanary 中文使用說明

http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

LeakCanary的記憶體洩露提示一般會包含三個部分:

第一部分(LeakSingle類的sInstance變數)

引用第二部分(LeakSingle類的mContext變數),

導致第三部分(MainActivity類的例項instance)洩露.

leakcanary使用注意

即使是空的Activity,如果檢測洩露時候遇到了如下這樣的洩露,注意,把refWatcher.watct()放在onDestroy裡面即可解決,或者忽略這樣的提示。

由於文章已寫很多,下面的就不再修改,忽略這種錯誤即可。

com.less.demo.TestActivity has leaked:
* GC ROOT static android.app.ActivityThread.sCurrentActivityThread
* references android.app.ActivityThread.mActivities
* references android.util.ArrayMap.mArray
* references array java.lang.Object[].[1]
* references android.app.ActivityThread$ActivityClientRecord.activity
* leaks com.less.demo.TestActivity instance
protected void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = App.getRefWatcher(this);
    refWatcher.watch(this);
}
3leakcanary和程式碼示例說明記憶體洩露

案例一(靜態成員引起的記憶體洩露)

public class App extends Application {
    private RefWatcher refWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher(Context context) {
        App application = (App) context.getApplicationContext();
        return application.refWatcher;
    }
}

測試內部類持有外部類引用,內部類是靜態的(GC-ROOT,將一直連著這個外部類例項),靜態的會和Application一個生命週期,這會導致一直持有外部類引用(內部類隱含了一個成員變數$0), 即使外部類制空= null,也無法釋放。

OutterClass

public class OutterClass {
    private String name;

    class Inner{
        public void list(){
            System.out.println("outter name is " + name);
        }
    }
}

TestActivity

public class TestActivity extends Activity {
  // 靜態的內部類
    private static OutterClass.Inner innerClass;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        OutterClass outterClass = new OutterClass();
        innerClass = outterClass.new Inner();

        RefWatcher refWatcher = App.getRefWatcher(this);
        refWatcher.watch(outterClass);// 監控的物件

        outterClass = null;
    }

案例二(單例模式引起的記憶體洩露)

DownloadManager

public class DownloadManager {
    private static DownloadManager instance;
    private Task task ;

    public static DownloadManager getInstance(){
        if (instance == null) {
            instance = new DownloadManager();
        }
        return instance;
    }
    public Task newTask(){
        this.task = new Task();
        return task;
    }
}

Task

public class Task {
    private Call call;
    public Call newCall(){
        this.call = new Call();
        return call;
    }
}

Call

public class Call {
    public void execute(){
        System.out.println("=========> execute call");
    }
}

TestActivity

public class TestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        RefWatcher refWatcher = App.getRefWatcher(this);

        Task task = DownloadManager.getInstance().newTask();
        Call call = task.newCall();
        call.execute();
        // 監控的物件        
        refWatcher.watch(call);
        call = null// 無法回收,DownloadManager是靜態單例,引用task,task引用了call,即使call置為空,也無法回收,切斷GC_ROOT 聯絡即可避免記憶體洩露,即置task為空。
    }
}


部分日誌列印如下:當前的GC_ROOT是DownloadManager的instance例項。

In com.leakcanary.demo:1.0:1.
com.less.demo.Call has leaked:
GC ROOT static com.less.demo.DownloadManager.instance
references com.less.demo.DownloadManager.task
references com.less.demo.Task.call
leaks com.less.demo.Call instance

關於上面兩種方式導致的記憶體洩露問題,這裡再舉兩個案例說明以加強理解。

案例三(靜態變數導致的記憶體洩露)