编程工具:Android Studio
分析内存工具:Android Studio和MAT
内存泄露的例子:
// MainActivity类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void openNextActivity(View view) {
// 跳转到内存泄露的Activity
startActivity(new Intent(this, SecondActivity.class));
}
}
// SecondActivity类 : 存在内存泄露
public class SecondActivity extends AppCompatActivity {
private List dataList = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
// 模拟储存10000个数据,这样更分析更明显
for (int i = 0; i < 10000; i++) {
dataList.add(new Student("test" + i, i, "school" + i));
}
new Thread(runnable).start();
}
public static class Student {
public String name;
public int age;
public String school;
public Student(String name, int age, String school) {
this.name = name;
this.age = age;
this.school = school;
}
}
// 内存类,依赖外部类,持有外部类SecondActivity实例
private Runnable runnable = new Runnable() {
@Override
public void run() {
// 模拟延时
try {
Thread.sleep(100 * 60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
我们可以利用Android Studio的【Monitors窗口】查看应用内存情况:
用于对比,先把存在内存泄露的Activity恢复正常:
// SecondActivity类 : 不存在内存泄露问题
public class SecondActivity extends AppCompatActivity {
private List dataList = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
// 模拟储存10000个数据,这样更分析更明显
for (int i = 0; i < 10000; i++) {
dataList.add(new Student("test" + i, i, "school" + i));
}
}
public static class Student {
public String name;
public int age;
public String school;
public Student(String name, int age, String school) {
this.name = name;
this.age = age;
this.school = school;
}
}
}
然后反复启动10-20次SecondActivity,内存使用情况是这样的:
从结果可以看到,几次GC之后,内存可以正常被回收。
我们来看看SecondActivity存在内存泄露的情况:
对比上一个图,几次GC之后,程序占用的内存一直上升,说明本该被回收的内存一直没有被释放。初步分析,存在内存泄露问题。
接下来我详细分析哪里存在内存泄露。
Android Studio自带的界面,查看内存泄露还不是很智能,我们可以借助第三方工具。
常见的工具就是MAT了,下载地址 http://eclipse.org/mat/downloads.php ,这里我们需要下载独立版的MAT。
hprof文件可以让Android Studio帮我们生成,具体做法是:
启动程序,MainActivity中点击按钮跳转到SecondActivity(初步分析,存在内存泄露),返回点击返回键,然后点击按钮跳转到SecondActivity,再点击返回,反复几次,然后点击【Monitors窗口】下【Momery】右边的【Dump Java Heap】按钮:
此时hprof文件已经生成了,在【Captrues窗口】可以找到:
但是此时的hprof文件还不能被MAT识别,我们右击我们需要的hprof文件,导出标准的hprof文件:
我们用MAT分析内存工具打开导出的hprof文件:
打开之后,点击工具栏中的直方图图标查看内存详细情况(就是哪些类使用了多少内存):
打开的界面会显示所有的内存使用情况,所以需要我们过滤一下(一般关键字为包名):
过滤之后显示的界面是这样的:
我们从这里可以看到,因为SecondActivity被我启动了7次,产生了7个SecondActivity实例,而且都没有回收,因为每个SecondActivity实例都保存了10000个Student实例,所以Student实例有70000个,我们查查SecondActivity为什么没有被回收。
右击【com.johan.demo.SecondActivity】,选择【Merge Shortest Paths to GC Roots】,然后选择【with all reference】:
然后展开一个查看:
发现SecondActivity实例被Thread匿名内部类的this$0引用,Thread还存活,所以持有的SecondActivity实例也认为是“存活”的,所以GC不能回收,造成内存泄露。
这就找到了内存泄露的原因!!
SecondActivity内存泄露的原因是静态内部类持有外部类导致的,我们这么改造一下:
public class SecondActivity extends AppCompatActivity {
private List dataList = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
for (int i = 0; i < 10000; i++) {
dataList.add(new Student("test" + i, i, "school" + i));
}
new Thread(new MyRunnable()).start();
}
public static class Student {
public String name;
public int age;
public String school;
public Student(String name, int age, String school) {
this.name = name;
this.age = age;
this.school = school;
}
}
// 声明为静态内部类
public static class MyRunnable implements Runnable {
@Override
public void run() {
// 模拟延时
try {
Thread.sleep(100 * 60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我们重新导出hprof文件,查看内存详细情况:
如图,MyRunnable对象有7个,证明SecondActivity已经启动了7次,而SecondActivity只剩下一个(应该是还没有来得回收),所以SecondActivity实例可以被GC正常回收了,内存泄露问题得以解决!!其实这里还有泄露,就是MyRunnable,我们关闭SecondActivity的时候,应该中断线程。不过还是建议使用线程池,靠谱!
目前除了导出hprof文件分析内存泄露,还有一种更简单的办法,就是在程序代码中集成LeakCanary。
项目地址:https://github.com/square/leakcanary
有兴趣的自己试一下!!!