内存泄漏 Memory Leak
内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
内存泄漏的危害
App可能因为大量的内存泄漏导致内存耗尽,引发Crash,如果内存未耗尽,App也会由于内存空间不足,出现频繁的GC,每一次GC都是非常耗时的阻塞性操作,会造成设备非常严重的卡顿。
示例
Handler:我们经常会在activity中这样使用handler:
1 | private class MyHandler extends Handler{ |
由于myHandler是Handler的非静态匿名内部类的实例,而这个非静态匿名内部类对其外部类存在一个隐式引用,其外部类在销毁之前,如果该非静态匿名内部类的handleMessage还未处理完成,将会导致外部类的内存资源无法正常释放,造成了内存泄漏。
非静态内部类创建静态实例
非静态内部类可以自由使用外部类的所有变量和方法,非静态内部类默认持有外部类的引用,此时如果在外部类创建静态static的非静态匿名内部类的实例(声明为static静态成员变量),或者在非静态内部类创建了一个静态实例, 这样就导致内部类的生命周期和应用程序ClassLoader一样长,导致外部类无法正常销毁。
分析
- 为什么非静态内部类对外部类会存在一个隐式引用?
- 为什么非静态内部类存在异步任务,可能会导致其对应的外部类内存资源无法正常释放?
- 为什么非静态内部类中创建了一个静态实例,会导致内存泄漏?
隐式引用
1 | public class MainActivity extends AppCompatActivity { |
在非静态匿名内部类中,我们可以访问到外部类的testMethod
方法,这个就是隐式引用的作用
对比非静态匿名内部类的源码和编译后的字节码
其中,args_size
:代表着隐式引用this的个数,一个是dispatchMessage
的,一个是Test$MyHandler
的,可以看出非静态匿名内部类中确实持有外部类的引用。
对比非静态内部类的源码和编译后的字节码
同样的,可以看出非静态内部类中确实持有外部类的引用。
结论
非静态内部类和非静态匿名内部类中确实持有外部类的引用,静态内部类中未持有外部类的引用。隐式引用是导致内存泄漏的根本原因。
解决思路
- 去除隐式引用(通过静态内部类来去除隐式引用)
- 手动管理对象(修改静态内部类的构造方式,手动引入其外部类引用)
- 当内存不可用时,不执行不可控代码(Android可以用
WeakReference
包裹外部类实例)