全方位帶你徹底搞懂Android記憶體洩露
1Java記憶體回收方式
Java判斷物件是否可以回收使用的而是可達性分析演算法。
在主流的商用程式語言中(Java和C#),都是使用可達性分析演算法判斷物件是否存活的。這個演算法的基本思路就是通過一系列名為"GC Roots"的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的,下圖物件object5, object6, object7雖然有互相判斷,但它們到GC Roots是不可達的,所以它們將會判定為是可回收物件。
2使用leakcanary檢測洩漏在Java語言裡,可作為GC Roots物件的包括如下幾種:
a.虛擬機器棧(棧楨中的本地變量表)中的引用的物件
b.方法區中的類靜態屬性引用的物件
c.方法區中的常量引用的物件
d.本地方法棧中JNI的引用的物件
摘自《深入理解Java虛擬機器》
關於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
關於上面兩種方式導致的記憶體洩露問題,這裡再舉兩個案例說明以加強理解。
案例三(靜態變數導致的記憶體洩露)