简单回顾下 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对象导致内存溢出,定位问题所在。
这只是一个简单的分析内存泄漏的例子,当然在实际工作中,情况也许会比这个复杂,要具体情况具体分析啦。