内存泄漏是Android开发必须重视的问题,它可能导致应用性能低下,内存抖动,甚至OOM.如何检测和分析OOM是必须要掌握的知识.本文将从三个方面来介绍内存泄漏问题,分别是内存泄漏的基础知识,常见的内存泄漏场景和内存泄漏分析和解决办法.
一. 内存泄漏的基本知识
1. 什么是内存泄漏?
没有用的对象无法被回收
2. 内存泄漏的危害?
• 应用可用的内存减少,增加了堆内存的压力
• 严重的时候可能会导致OOM Error
• 触发更频繁的GC ,造成卡顿,内存抖动等现象
3. 为什么会内存泄漏?
长生命周期的对象持有短生命周期对象“强/软引用”,导致本应该被回收的短生命周期的对象却无法被正常回收。
4. 对象什么时候被回收——可达性分析算法介绍
可达性分析算法能够被GC Root引用的对象是否可达
GC Root
由GCRoot不得不说一下Java内存模型了
java内存模型
简单的介绍一下:
1.程序计数器:线程私有,记录各个线程运行的位置
2.虚拟机栈:每个method,线程私有,存放局部变量,方法结束后自动释放内存。
3.本地方法栈:native method。
4.方法区:它主要存放静态数据,全局变量,编译时就分配好,在程序整个运行期间都存在。
5.堆:通常用来存放 new 出来的对象。由 GC 负责回收。
二,常见的内存泄漏场景
注意:Activity 内存泄漏预防
Activity内部持有大量的资源引用以及与系统交互的 Context,这会导致一个 Activity 对象的 retained size 特别大。一旦 Activity 因为被外部系统所持有而导致发生内存泄漏,被牵连导致其他对象的内存泄漏也会非常多。
这里介绍一下Retained Size:
Shallow size:对象本身占用内存的大小。 Retained Size: 对象本身的Shallow Size + 对象能直接或间接访问到的对象的Shallow Size
1.静态变量造成的内存泄漏——将 Context 或者 View 置为 static,
原因:静态变量的生命周期与Application相同,编译时就分配好,在程序整个运行期间都存在。
由于static handler中要使用view进行更新,这里提供的方法是去掉static 修饰,提供一个方法,获得对应的对象。
2. 非静态 Handler 导致 Activity 泄漏
原因:由于 Handler 属于 TLS(Thread Local Storage)变量,对应GCRoot:仍处于存活状态中的线程对象,导致它的生命周期和 Activity 不一致。
3.单例或三方库使用Activity context
原因:由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,一不小心让单例无限制的持有 Activity 的强引用就会导致内存泄漏。
解决方案:把传入的 Context 改为同应用生命周期一样长的 Application 中的 Context。两种方式:
1.context.getApplicationContext()。
- 通过重写 Application,提供 getContext ()方法,那样就不需要在获取单例时传入 context。
提示:
1.在实现 SDK 时,也尽量避免造成外部 Context 的泄漏。对传入的Context,主动转换为application Context
2.并不是所有的context能用applicationContext。
Context使用规则
• NO1 表示 Application 和 Service 可以启动一个 Activity,不过需要创建一个新的 task 任务队列。
• 对于 Dialog 而言,只有在 Activity 中才能创建。
5.非静态内部类和匿名类导致的内存泄漏
使用非静态内部类和匿名类都会默认持有外部类的引用,如果生命周期不一致,就会导致内存泄漏。
将该内部类设为静态内部类 也可以将该内部类抽取出来封装成一个单例,Handler 泄漏也属于这种.
6. Listener 注册和解绑的声明周期不一致导致
Activity的onResume 注册,在onDestory 解绑,导致多次注册listener 并添加到对应的list中,只解锁最后一个导致内存泄漏。6.广播,File,IO流,Cursor, Listener资源未释放
三,内存泄漏检测分析实践
3.1 分析已知的内存泄漏
方式一:MAT分析和使用技巧
MAT是Memory Analyzer tool的缩写,是一种快速,功能丰富的Java堆分析工具,能帮助你查找内存泄漏和减少内存消耗。
MAT 的使用技巧
技巧一:通过OQL(Objective Query Language )查询对象
由于内存泄漏一般发生在Activity中,查询Activity对象
技巧二:通过搜索关键字发现内存泄漏对象
方式二:Android Profile 内存泄漏分析
Android Profiler是Android Studio3.0用来替换之前Android Monitor的观察工具,Android Profile 可以分析CPU,Memory,Network,Energy 1.点击View > Tool Windows > Profiler或者点击工具栏的图标即可打开Load file 或者进程Android Profiler hprof内存泄漏分析
Android Profile 进程内存泄漏分析
这里GC的原因是回收掉软/弱/虚引用。3.2 寻找并解决未知的内存泄漏
LeakCanary + Monkey + MAT/Profiler
LeakCanary介绍和使用
LeakCanary 是 Square 公司的一个开源库。通过它可以在 App 运行过程中检测内存泄漏,当内存泄漏发生时会生成发生泄漏对象的引用链,
并通知程序开发人员。
- App build.gradle 添加依赖,在logcat查看LeakCanary关键字确定是否集成成功
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
} - Android TV 注意项
Monkey简介
monkey -p com.mediatek.wwtv.tvcenter -c android.intent.category.DEFAULT --throttle 500 --pct-nav 25 --pct-majornav 25 --pct-syskeys 25
--pct-appswitch 25 --ignore-crashes --ignore-timeouts --ignore-security-exceptions --kill-process-after-error -v -v 10000
命令解释:
-c 指定activity的category类别,注意activity应该指定,否则测试的脚本不会执行该Activity
--throttle:后面接时间,单位为ms,表示事件之间的固定延迟(即执行每一个指令间隔的时间),如果不接该项,monkey将不会延迟
--pct-nav:后面接基本导航事件百分比,主要来自方向输入设备的上、下、左、右事件
--pct-marjornav:后面接主要导航事件百分比,通常指引发图形界面的一些动作,如键盘中间按键、返回按键、菜单按键等
--pct-syskeys:后面接系统按键事件百分比,通常指仅供系统使用的保留按键,如HOME键、BACK键、拨号键、挂断键、音量键等
--pct-appswitch:后面接应用启动事件百分比,应用启动事件(activity launches)即打开应用,通过调用startActivity()方法最大限度地开启该package下的所有应用
--pct-touch:后面接触摸事件百分比,触摸事件泛指发生在某一位置的一个down-up事件,点击
--pct-motion:后面接动作事件百分比,动作事件泛指从某一位置接下(即down事件)后经过一系列伪随机事件后弹出(即up事件)
-v -v指定monkey报告等级,一个 -v增加一个级别,默认缺省值是0级,
-v,Level 0(缺省值),除启动提示、测试完成和最终结果之外,提供较少信息
-v -v,Level 1,提供较为详细的测试信息,如:逐个发送到Activity的事件
-v -v -v,Level 2,提供更加详细的设置信息,如:测试中被选中的或未被选中的Activity
3. TV 端查看泄漏列表详情
adb shell am start -n "com.xxx/leakcanary.internal.activity.LeakLauncherActivity"泄漏详情
可以看到GC root和引用链.
Logcat也可查看泄漏
这里可以看到hprof文件位置,如果不是很明显可以结合MAT或profiler进行分析。
LeakCanary 原理浅析
LeakCanary 号称在App 运行过程中检测内存泄漏,当内存泄漏发生时会生成发生泄漏对象的引用链那它如何检测内存泄漏?
实现说一下WeakReference和ReferenceQueue
使用强引用对象:before gc, reference.get is com.danny.lagoumemoryleak.WeakRefDemo$BigObject@7852e922
before gc, queue is null
after gc, reference.get is null
after gc, queue is java.lang.ref.WeakReference@4e25154f
log输出
before gc, reference.get is com.danny.lagoumemoryleak.WeakRefDemoBigObject@7852e922
after gc, queue is null
可以看出未GC成功,queue为空.这里请注意一下,这里的bigobject对象有强引用和虚引用两种对象,只有只包含虚引用的对象在GC时会被回收.
LeakCanary 中对内存泄漏检测的核心原理就是基于 WeakReference 和 ReferenceQueue 实现的。
1.当一个 Activity 需要被回收时,就将其包装到一个 WeakReference 中,并且在 WeakReference 的构造器中传入自定义的 ReferenceQueue。
2.然后给包装后的 WeakReference 做一个标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录
3.最后主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set 中的记录也删除
经过上面 3 步之后,还保留在 Set 中的就是:应当被 GC 回收,但是实际还保留在内存中的对象,也就是发生泄漏了的对象。
在一个Activity执行完onDestroy()之后,将它放入WeakReference中,然后将这个WeakReference类型的Activity对象与
ReferenceQueque关联。这时再从ReferenceQueque中查看是否有没有该对象,如果没有,执行gc,再次查看,还是没有的话则判
断发生内存泄漏了。最后用HAHA这个开源库去分析dump之后的heap内存。
网友评论