内存泄露检测 LeakCanary
LeakCanary 是 Square 公司的开元哭, 通过它可以在App 运行的过程中检测内存泄露, 当内存泄露发生时会生产发生泄露对象的引用链, 并通知开发人员
使用
debugImplementation 'com.squareup.leakcannary:leakcannary-android:2.7'
执行流程
heap dump
- 检测保留的对象
- 生成堆 dump 文件
- 分析堆 dump 文件
- 堆泄露进行分类
检测对象类型
- 已销毁的 Activity 实例
- 销毁的 Fragment 实例
- 已清除的 ViewModel 实例
四种引用
强引用
被强引用引用或者关联的对象, 在GC 之后不会被回收掉被引用的对象
软引用 SoftReference
- 一些有用但是非必须,用软引用关联的对象,系统将发生内存溢出OOM之前,这些对象会被回收(如果这次回收之后还没有足够空间,才会抛出内存溢出)
弱引用 WeakReference
- 一些有用但是并必需,用弱应用关联对象,在 GC 的时候进行回收;
- 前提这个弱引用引用的一个强引用对象未空的, 没有被
虚引用 PhantomReference
原理
-
通过 hook Android 的生命周期来自动检测 Activity 和 Fragment 被销毁,这些被 destroy 的对象会被传递给 ObjectWatcher, ObjectWatcher 持有对他们的弱引用。
-
弱引用和引用队列
ReferenceQueue 与 WeakReference 弱引用和引用队列
- 弱应用(WeakReference) 和一个引用队列(ReferenceQueue)联合使用, 如果弱引用所引起的对象被垃圾回收器回收, 虚拟机就会把这个弱应用加入到与之管理的引用队列中, 我们可以用此特性来检测一个对象是否被来及回收器回收成功。
核心流程
- 监控列表 、 保留列表
- 弱引用的流转过程
源码
- ActivityWatcher
- FragmentAndViewModelWatcher
- ViewModelClearedWatcher
- ServiceWatcher
常见内存泄漏场景
静态实例持有
单例持有上下文
因为单例的静态特性,如果他应用了Activity 的上下文,那么在 Activity 生命周期结束后,activity 销毁,但是这个引用不能被GC 回收,导致内存泄漏
静态变量持有上下文
和单例原理一样
非静态内部类
自定义handler 非静态内部类
-
内部类不管匿名还是有名,一定持有外部类的引用;
-
定义一个 handler 匿名的内部类, 当时Activity销毁之后,handler 中还有消息的存在,那么 Message 持有Handler , handler 持有外部类 Activity , 造成内存泄露;
-
并且会有 activity is running 的异常抛出的可能;
改写方法
- 写成静态类的方式,
- 持有Activity 的弱引用
- 在Activity 生命周期结束之后, 全部移除消息队列中的消息;
未取消注册或毁掉导致
- 广播 ,在Activity 销毁时要注销监听,因为广播注册持有Activity ,原理非静态内部类持有外部类引用,
- 观察者模式的注册 ,在销毁时也要注销
- Retrofit+RxJava 注册的网络请求毁掉,同样是非静态内部类持有外部类,在销毁时也要注销
Timer和TimerTask
集合中的对象未清理
- 集合中对象,当不再使用的时候,应该从集合中溢出,
- 如果集合被静态引用的话, 集合里面那些没有用的对象更会造成内存泄漏, 所以在使用集合时要及时将不用的对象从集合中溢出。
资源未关闭或未释放
使用IO、File 或者Sqlite、Cursor 等资源时要及时关闭, 这些资源在进行读写操作时通常都使用了缓冲,如果不及时关闭,这些缓冲对象就会一直被占用而得不到释放,
因此我们在不需要使用他们的时候及时关闭,从缓冲期及时能释放,
属性动画
属性动画是一个定时任务,在销毁时没有关闭动画,那么多动画会不断的播放下去,动画引用的控件, 控件引用Activity,所以造成Activity无法释放, 内存泄露;
webview
- 因为 webview 在加载网页之后会长期占用内存而不能释放, 因为我们在Activity 销毁后要调用它的 destory 方法来销毁它释放内存。
- 另外 webview 中的 callback 持有Activity ,及时调用destory 也无法解决,
- 最终的方法从中移除掉 callback 之后在销毁 webview。