一、Java的内存分配模型          

       在分析Android内存泄漏之前,先简单谈一下Java的内存分配模型。我们可以简单讲内存分为三个区:静态存储区、堆和栈。然后我们再简单看下这三个分区分别存储什么类型的数据。静态存储区会存储你所定义的一些静态变量,它的生命周期是和你的应用的生命周期是一致的。栈中是存放局部变量和对象的引用的,它的生命周期是在所在作用域内存在的,当执行完所在作用域后内存就被自动回收了。堆中是存放具体的对象的,他的内存回收是通过GC按照一定的算法进行回收的。我们所说的内存泄漏就发生在堆中。

二、为什么会发生内存泄漏

      我们都知道堆中的内存是需要靠GC进行回收的,那么JVM是如何进行回收的呢?如何判定哪块内存应该回收而哪块内存不应该回收呢?这就要说到Java的内存回收机制。如果用有向图来描述应用中实例间的联系,简单点来说,对于jvm当从根(main方法)不能到达的点就被定义为不再有引用持有的对象,这类对象的内存开销是需要被JVM进行回收的。如果一个对象将来不再被使用了,却能从main开始能到达,这就造成了该对象不能被回收,从而造成内存的泄漏。所以可以看出来内存泄漏可能就是因为在开发过程中的一些不规范或者错误的使用而导致的,有时候内存泄漏从短期来看并不会对你的应用造成多大的影响,但是一旦随着时间的积累可能在未知的时候(OOM)带来灾难性的打击。所以从一开始就应该避免内存泄漏的发生。

三、Android开发中造成内存泄漏的坑

1、单例模式

      如果你需要构造一个单例的对象,恰好它需要一个Context类型的参数进行初始化。如果你将Activity的实例传入,那么此单例就持有了该Activity的引用。由于单例是静态的,其生命周期是和应用的生命周期是一致的。当Activity使用完以后并不会被回收,这样就造成了activity中的内存泄漏。解决方式是使用Application的context,因为Application的生命周期是和应用的生命周期是一致的。如果Application的context无法满足而必须使用activity的context,那么可以使用软引用或者弱引用。这样在应用内存不足时,gc可以回收这部分内存。

2、内部类

     a、如果你的内部类是非静态的,而外部类又持有一个静态的内部类实例:

          原因: 因为非静态内部类默认会持有一个外部类实例的引用,而外部类又持有静态的内部类实例,因为静态的实例的生命周期是和应用的生命周期一致的。所以静态内部类实例会和应用的生命周期一样,而它又持有外部类的引用,所以外部类的内存也无法被释放。

     解决方法:将内部类声明为静态的,这样就不会默认持有外部类的引用了。

     b、将匿名内部类放到一个异步线程中:

          原因:也是因为非静态内部类持有外部类的引用,而当把匿名内部类的引用放到一个生命周期和外部类不一致的线程中时,就有可能会造成内存泄漏。

     c、使用匿名内部类的方式使用Handler

          原因:与上一致

          解决方法:将Handler声明为静态的,并且在activity被销毁时(onStop或者onDestroy)中将未执行完的message移除。

3、资源未关闭

      对于使用了BroadcastReceiver、ContentProvider、File、Cursor、Stream、Bitma等资源的使用,应该在Activity被销毁时或者不再使用时及时关闭或者注销,否则资源不被回收造成内存泄漏。注意Bitma释放时一定要主动调用bitmap.recycle()方法。

四、如何分析内存泄漏

       这里介绍两种工具MAT和LeakCanary。后续会专门详细介绍如何利用这两个工具去调试内存泄漏。