欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 内存泄露分析

程序员文章站 2024-01-24 22:20:34
...

编程工具: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<Student> 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窗口】查看应用内存情况:

Android 内存泄露分析

用于对比,先把存在内存泄露的Activity恢复正常:

// SecondActivity类 : 不存在内存泄露问题
public class SecondActivity extends AppCompatActivity {
    private List<Student> 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,内存使用情况是这样的:

Android 内存泄露分析

从结果可以看到,几次GC之后,内存可以正常被回收。

我们来看看SecondActivity存在内存泄露的情况:

Android 内存泄露分析

对比上一个图,几次GC之后,程序占用的内存一直上升,说明本该被回收的内存一直没有被释放。初步分析,存在内存泄露问题。

接下来我详细分析哪里存在内存泄露。

进一步分析

Android Studio自带的界面,查看内存泄露还不是很智能,我们可以借助第三方工具。
常见的工具就是MAT了,下载地址 http://eclipse.org/mat/downloads.php ,这里我们需要下载独立版的MAT。

Android 内存泄露分析

生成hprof文件

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

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

Android 内存泄露分析

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

Android 内存泄露分析

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

Android 内存泄露分析

分析hprof文件

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

Android 内存泄露分析

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

Android 内存泄露分析

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

Android 内存泄露分析

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

Android 内存泄露分析

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

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

Android 内存泄露分析

然后展开一个查看:

Android 内存泄露分析

发现SecondActivity实例被Thread匿名内部类的this$0引用,Thread还存活,所以持有的SecondActivity实例也认为是“存活”的,所以GC不能回收,造成内存泄露。

这就找到了内存泄露的原因!!

SecondActivity内存泄露的原因是静态内部类持有外部类导致的,我们这么改造一下:

public class SecondActivity extends AppCompatActivity {
    private List<Student> 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 内存泄露分析

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

LeakCanary

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

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

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