Android非静态内部类导致内存泄漏原因深入剖析

内存泄漏 Memory Leak

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

内存泄漏的危害

App可能因为大量的内存泄漏导致内存耗尽,引发Crash,如果内存未耗尽,App也会由于内存空间不足,出现频繁的GC,每一次GC都是非常耗时的阻塞性操作,会造成设备非常严重的卡顿。

示例

Handler:我们经常会在activity中这样使用handler:

1
2
3
4
5
private class MyHandler extends Handler{
...
}
//使用
MyHandler myHandler = new MyHandler(this);

由于myHandler是Handler的非静态匿名内部类的实例,而这个非静态匿名内部类对其外部类存在一个隐式引用,其外部类在销毁之前,如果该非静态匿名内部类的handleMessage还未处理完成,将会导致外部类的内存资源无法正常释放,造成了内存泄漏。

非静态内部类创建静态实例

非静态内部类可以自由使用外部类的所有变量和方法,非静态内部类默认持有外部类的引用,此时如果在外部类创建静态static的非静态匿名内部类的实例(声明为static静态成员变量),或者在非静态内部类创建了一个静态实例, 这样就导致内部类的生命周期和应用程序ClassLoader一样长,导致外部类无法正常销毁。

分析

  1. 为什么非静态内部类对外部类会存在一个隐式引用?
  2. 为什么非静态内部类存在异步任务,可能会导致其对应的外部类内存资源无法正常释放?
  3. 为什么非静态内部类中创建了一个静态实例,会导致内存泄漏?

隐式引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private void testMethod(){

}
private class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
testMethod();
//MainActivity.this 这个就是隐式引用
MainActivity.this.testMethod();
}
}
}

在非静态匿名内部类中,我们可以访问到外部类的testMethod方法,这个就是隐式引用的作用

对比非静态匿名内部类的源码和编译后的字节码

其中,args_size:代表着隐式引用this的个数,一个是dispatchMessage的,一个是Test$MyHandler的,可以看出非静态匿名内部类中确实持有外部类的引用。

对比非静态内部类的源码和编译后的字节码

同样的,可以看出非静态内部类中确实持有外部类的引用。

结论

非静态内部类和非静态匿名内部类中确实持有外部类的引用,静态内部类中未持有外部类的引用。隐式引用是导致内存泄漏的根本原因。

解决思路

  1. 去除隐式引用(通过静态内部类来去除隐式引用)
  2. 手动管理对象(修改静态内部类的构造方式,手动引入其外部类引用)
  3. 当内存不可用时,不执行不可控代码(Android可以用WeakReference包裹外部类实例)