簡單回顧下 JAVA 的內存回收機制,內存空間中垃圾回收的工作由垃圾回收器 (Garbage Collector,GC) 完成的,它的核心思想是:對虛擬機可用內存空間,即堆空間中的對象進行識別,如果對象正在被引用,那麼稱其爲存活對象,反之,如果對象不再被引用,則爲垃圾對象,可以回收其佔據的空間,用於再分配。
在垃圾回收機制中有一組元素被稱爲根元素集合,它們是一組被虛擬機直接引用的對象,比如,正在運行的線程對象,系統調用棧裏面的對象以及被 system class loader 所加載的那些對象。堆空間中的每個對象都是由一個根元素爲起點被層層調用的。因此,一個對象還被某一個存活的根元素所引用,就會被認爲是存活對象,不能被回收,進行內存釋放。因此,我們可以通過分析一個對象到根元素的引用路徑來分析爲什麼該對象不能被順利回收。如果說一個對象已經不被任何程序邏輯所需要但是還存在被根元素引用的情況,我們可以說這裏存在內存泄露。
下面我們自己寫一個存在明顯的內存泄漏的demo,然後通過內存分析,定位問題所在。(還是老套路,Activity下有兩個Button,一個Begin一個End)
package test.memoryLeak;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import com.cxq.selftestdemo.R;
import java.util.ArrayList;
public class TestMemoryActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_wifi);
}
class Person {
String name;
Person(String name) {
this.name = name;
}
}
boolean isRun = true;
ArrayList<Person> list = new ArrayList<>();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (isRun) {
Person s = new Person("test");
list.add(s);
}
}
});
/**
* 按下begin按鈕
*
* @param v
*/
public void begin(View v) {
thread.start();
}
/**
* 按下end按鈕
*
* @param v
*/
public void end(View v) {
isRun = false;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
很明顯,這段代碼存在內存泄漏
while (isRun) { Person s = new Person("test"); list.add(s); s=null; }Person對象s雖然在最後被賦值成null,但由於list對象仍然持有對象s的引用,因此s一直被新建,並且一直沒有釋放,因此內存就泄漏了。
下面講解如何分析內存泄漏:
【環境】
1、Android Studio
2、內存分析工具MAT(Memory Analysis Tools)
【步驟1】得到hprof文件
在Android Studio的Android Monitor,選擇你的進程,然後點擊左上角第3個圖標, Dump Java Heap
這樣在工程目錄下的captrue文件夾下就會產生hprof文件,但是這個不是標準的hprof文件,需要經過轉換才能被MAT打開分析,轉換操作如下
自己隨便命名,我這邊命名成1.hprof
【步驟2】使用MAT分析內存情況
打開MAT,open File,選擇剛纔轉換好的標準hprof文件(我的是1.hprof),打開
點擊Leak Suspects,裏面會列出問題可疑點,餅圖的下方是可以點的具體描述,我們找到自己寫的那個類相關的可疑點,點擊Details
進去查看引用的路徑以及類的分配情況
發現是在一個線程中不斷的新建Person對象導致內存溢出,定位問題所在。
這只是一個簡單的分析內存泄漏的例子,當然在實際工作中,情況也許會比這個複雜,要具體情況具體分析啦。