1.关于内存泄漏
- 对象占用内存资源,如果某个对象没用了就可以回收它占用的资源,给别的对象用(内存回收,garbage collection)。
- 对象有没有用是根据可达性算法判断的(当一个对象到可以作为GCRoot的对象之间没有引用链时,就表示这个对象没用了)
- 假如某个时刻,从业务角度认为某个对象没用了,但是由于代码没处理好导致引用链还存在,导致应该回收的内存空间没有被回收,就是内存泄漏了,是对内存空间的浪费。
2.配置LeakCanary用于检测内存泄漏
- 在app的build.gradle的dependencies添加依赖:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
- 同步依赖,打包运行,看到logcat打印
D/LeakCanary: LeakCanary is running and ready to detect leaks
就说明配置好了。在手机上可以看到Leaks app。LeakCanary可以监控activity等组件destroy的时候是否还有引用链。
3.使用Handler引起内存泄漏的例子
- 在activity里创建一个Handler对象(这个对象是一个继承了Handler的匿名内部类的实例)
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
- 在onCreate方法里让handler延时10s发送消息
handler.sendEmptyMessageDelayed(1, 10000);
- 打包运行,打开activity再后退。等大概10s的时候会看到leaks通知说检测到了没有被回收的对象,这个时候点一下通知就会导出此刻的堆内存快照,如果不及时点导出的话,leakcanary会默认等2s再检测一遍,2s之后消息已经发送处理完毕了,所以就会又弹出一个提示说所有垃圾都已经回收了。
- 打开leaks app,看到内存泄漏的引用链:
GC Root --> MessageQueue.mMessages --> Message.target --> LeakTestActivity\$1.this\$0
这个引用链从左到右:
- MessageQueue是主线程的消息队列,生命周期和主线程相同;
- Message发送完就从MessageQueue移除了,生命周期10s;
- Message.target是处理这个消息的handler(也是发送这个消息的handler,handler自己发自己收);
- handler是LeakTestActivity的非静态内部类,所以持有外部类的引用this(因为内部类是通过外部类的引用去访问外部类的成员属性、方法)
- 所以Message没有从MessageQueue移除的时候,LeakTestActivity是会被引用到的,所以如果这个时候关闭activity就会内存泄漏。(不过消息处理完之后这块内存还是可以回收的,不至于像静态变量持有引用那样泄漏得那么彻底)
4.避免handler引起的内存泄漏
可以通过以下做法让handler不要持有activity的引用:
- 把继承Handler的内部类声明为static
- 如果在handleMessage的时候需要用到activity引用也声明为WeakReference<>
- 在onDestroy方法里removeCallbacksAndMessages