Android 内存泄露分析

编程工具: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。

Android 内存泄露分析_第1张图片

生成hprof文件

hprof文件可以让Android Studio帮我们生成,具体做法是:

启动程序,MainActivity中点击按钮跳转到SecondActivity(初步分析,存在内存泄露),返回点击返回键,然后点击按钮跳转到SecondActivity,再点击返回,反复几次,然后点击【Monitors窗口】下【Momery】右边的【Dump Java Heap】按钮

Android 内存泄露分析_第2张图片

此时hprof文件已经生成了,在【Captrues窗口】可以找到:

但是此时的hprof文件还不能被MAT识别,我们右击我们需要的hprof文件,导出标准的hprof文件:

Android 内存泄露分析_第3张图片

分析hprof文件

我们用MAT分析内存工具打开导出的hprof文件:

Android 内存泄露分析_第4张图片

打开之后,点击工具栏中的直方图图标查看内存详细情况(就是哪些类使用了多少内存):

打开的界面会显示所有的内存使用情况,所以需要我们过滤一下(一般关键字为包名):

Android 内存泄露分析_第5张图片

过滤之后显示的界面是这样的:

Android 内存泄露分析_第6张图片

我们从这里可以看到,因为SecondActivity被我启动了7次,产生了7个SecondActivity实例,而且都没有回收,因为每个SecondActivity实例都保存了10000个Student实例,所以Student实例有70000个,我们查查SecondActivity为什么没有被回收。

右击【com.johan.demo.SecondActivity】,选择【Merge Shortest Paths to GC Roots】,然后选择【with all reference】:

Android 内存泄露分析_第7张图片

然后展开一个查看:

Android 内存泄露分析_第8张图片

发现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文件,查看内存详细情况:

Android 内存泄露分析_第9张图片

如图,MyRunnable对象有7个,证明SecondActivity已经启动了7次,而SecondActivity只剩下一个(应该是还没有来得回收),所以SecondActivity实例可以被GC正常回收了,内存泄露问题得以解决!!其实这里还有泄露,就是MyRunnable,我们关闭SecondActivity的时候,应该中断线程。不过还是建议使用线程池,靠谱!

LeakCanary

目前除了导出hprof文件分析内存泄露,还有一种更简单的办法,就是在程序代码中集成LeakCanary。

项目地址:https://github.com/square/leakcanary

有兴趣的自己试一下!!!

你可能感兴趣的:(Android,内存)