Android Memory定位内存泄漏

Android内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到 gc roots 导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。

泄漏有哪些危害

运行性能的问题: Android在运行的时候,如果内存泄露导致其他组件可用的内存变少,一方面会使得GC的频率加剧,在发生GC的时候,所有进程都必须进行等待,GC的频率越多,从而用户越容易感知到卡顿。另一方面,内存变少,将可能使得系统会额外分配给你一些内存,而影响整个系统的运行状况。运行崩溃问题: 一旦内存不足以分配某些内存,那么将会导致崩溃,这对于体验而言是致命的。我们在进行内存分析的时候,可以发现总有一些机型会出现OutOfMemory的崩溃栈,大抵都和内存泄露有关。

定位泄漏的方法

女生的小情绪,往往难以琢磨,如果我们忽略这些小脾气,往往又会得到惩罚。因而我们需要寻找一种方法来定位可能的对于内存妹纸的伤害。我们将分别从两个方面,来帮助我们分析和定位。一是宏观方法,通过一些很简单的方法来判断是否存在泄露,另一方面是通过精确定位的方式来提出具体的解决方案。

1)Android Studio Memory Monitor

Android Studio 提供了非常方便的工具,便于我们定位问题。AndroidMonitors模块中含有 Memory Tab,这个Tab以流线的方式,展示了每一时刻内,已分配的内存和还空闲的内存。

图中浅蓝颜色的部分表示已经分配的内存,而灰色部分表明空闲的内存


总选中Devices和相应的包名后,就能看到动态内存分配的情况。

如下图所示,当已分配的内存剧烈下降的时候,就标明发生了GC事件,GC发生的时刻和频率是我们关注的重点。

我们回到刚才的那个例子 #泄漏示例# ,下图是点击MainActivity然后按返回键退出,再进入再退出,重复几次后,内存Monitor显示的结果。从这个例子中,我们可以看到,尽管进过了几次 GC,但是内存用量却一直在增大,说明有些对象被某些静态或者其他GC Roots的对象引用着,导致其不能被释放。因而可以说明,其存在比较严重的内存泄漏问题

2)Android Devices Monitor

Android Devices Monitor提供了比较方便辅助的定位方法,在 Heap Tab下面,显示着% Used的使用量,如果这个值在GC后没有明显下降,那么就意味着发生了内存泄漏,具体的操作步骤如下。

1.    选择DDMS视图,并打开Devices视图和Heap视图

2.    点击选择要监控的进程,比如:上图中我选择的是system_process

3.    选中Devices视图界面上的updateheap图标

4.    点击Heap视图中的CauseGC 按钮(相当于向虚拟机发送了一次GC请求的操作)

第一次点击 CauseGC 后的内存占比


在多次退出和进入后的内存占比


3)精确定位方法

我们在查看是否存在内存泄漏情况的时候,基于的基础单位往往是Activity,因而就可以想到一种思路,即通过在界面回退后,强制进行GC,然后判断是否还存在对该Activity的引用,这样就能得知是否存在泄漏。


MAT 使用简介

具体的的实施步骤如下:

1.    客户端中打开相应的Activity,并执行可能触发内存泄漏的操作.

2.    退出Activity界面,并点击Initiate GC(左起第二个按钮)

3.    点击Dump Java Heap,等待一会后,这个时候可以看到Dump 出来的日志。

4.    由于Android Profile文件不被 MAT 支持,因为我们需要执行转换操作。 ./hprof-convpath/file.hprof exitPath/heap-converted.hprof

5.    在 MAT 中打开文件,并选择Leak Suspects Report,等待最后的结果。

6.    Select* From instanceof android.app.Activity 通过Activity的类名来过滤信息,在右键菜单里面,分别点击MergePaths to Shortest GC Root 和 exclude allphantom/weak/soft etc. references, 排除被弱引用持有的情况。

4)LeakCanary 自动定位

Square 开源了LeakCanary来用作对于内存泄露情况的自动检测。

LeakCanary实现了引用观察者RefWatcher。RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。通过在Activity重要的生命周期中,在后台线程检查引用是否被清除,如果没有,调用GC。如果在GC后,引用还是未被清除,那么可能发生了内存泄露,这时候把heap内存dump到 APP 对应的文件系统中的.hprof 文件中。在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA来解析这个文件。得益于唯一的 reference key, HeapAnalyzer 找到KeyedWeakReference,定位内存泄露。HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。

可以看到,Square在使用LeakCanary并进行相应的修改后,效果还是相当不错的。

由于官方开源的LeakCanary只能在Debug版上使用,在Release上通过NullObject方式实现了一个空实现,来避免性能问题。如果想通过小流量的方式来批量地发现用户内存泄露的情况,那么就需要对源码进行整改,刚好我做了这么一件事情,有兴趣的人可以拿去使用。移除UI展示等逻辑后可在Release上使用的LeakCanary。有了用户相关的数据的泄露栈就能很好地处理各种泄露问题,使得应用良好稳定地运行。

常见内存泄漏CASE与修复方法

泄漏CASE

1.    注册对象未反注册
在组件启动后,注册了某个对象的观察者,在组件回收的时候,忘记取消注册了。可以参考这样的例子,Activity声明的时候实现了对于下载进度接口的监听,而这个监听接口在实现的时候使用的是强引用,如果不进行主动反注册,Activity会因为被下载库持有引用,从而导致无法回收。

2.    长线执行的异步任务
组件内部有一个可能长时间执行的任务,通过内部类持有了对组件的引用。想象这样一个场景,界面上的某一个组件需要异步地去请求天气数据,在得到结果后显示在界面上。在网络回调的Callback中,持有了这个组件,从而在网络请求执行过程中,组件是无法进行回收的。

3.    Android SDK的泄露
这类泄露一般不严重,不用特殊处理。比如TextLine.sCached对象会持有一个拥有三个TextLine的对象池,但TextLine的回收方法recycle处理得有bug,在android-5.1.0_r1修复了一部分,修复连接。其他的泄露地方可从这里看出一部分,SDK泄露统计。

4.    类的静态变量持有大数据对象
静态变量长期维持到大数据对象的引用,阻止垃圾回收。

5.    资源对象未关闭象
资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。

6.    Handler 泄漏
Handler
通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。handler在使用过后,在组件退出的时候没有处理这些handler。通过Handler post出去一个任务后,没有在最后调用removeCallbacks的接口,清除掉所有跟这个Runnable相关的message。

修复方法

1.    尽量避免在组件内部使用内部类,内部的一些逻辑类可以使用Static的声明,避免持有对组件的引用。

2.    如果一定要持有内部类的引用,可以通过WeakReference来进行封装,这样可以缓解掉一些泄漏情况。

3.    对于Handler使用较多的情况,可以考虑使用WeakHandler

4.    正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。

5.    在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。

6.    对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。

 




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值