Android内存泄漏监控和优化技巧总结

前言

对于Android平台的应用程序来说,内存优化一直是个热门话题,与传统PC应用程序不同,Android上的应用一旦出现各种异常时系统默认会以最严厉的“崩溃”方式反馈给用户,如果处理不当,将严重影响用户体验。 丛所周之,移动设备的软硬件资源无法与传统PC相提并论(至少目前是这样),因而开发人员在编写应用时,需要有更多技巧、更精深的技术来应对各种局面。这其中尤以内存OOM(内存溢出)等涉及内存泄漏这样的问题最为常见。 本文着重总结降低应用内存占用的技巧以及对应的解决方案。

先来谈谈内存泄漏的监控机制

内存泄露:简单来说对象由于编码错误或系统原因,仍然存在着对其直接或间接的引用,导致系统无法进行回收。内存泄露,容易留下逻辑隐患,同时增加了应用内存峰值与发生OOM的概率。它属于bug issue,是我们一定要修改的。下面是造成内存泄露的一些常见原因,但是如何建立一套发现内存泄露、解决内存泄露的闭环方案,才是我们工作的重点。

1监控方案

Square的开源库leakcanry是一个非常不错的选择,它通过弱引用方式侦查Activity或对象的生命周期,若发现内存泄露自动dump Hprof文件,通过HAHA库得到泄露的最短路径,最后通过notification展示。 内存泄露判断与处理的流程如下图 ,各自运行的进程空间(主进程通过idlehandler,HAHA分析使用的是单独的进程):

微信在leakcanry推出之前已经有了自己的内存泄露监控体系,与leakcanry大致有以下的区别:

事实上,通过对leakcanry做简单的定制,我们就可以实现以下一个内存泄露监控闭环。

2内存泄露后的挽救措施

Activity泄漏会导致该Activity引用到的Bitmap、DrawingCache等无法释放,对内存造成大的压力,挽救措施是指对于已泄漏Activity,尝试回收其持有的资源,泄漏的仅仅是一个Activity空壳,从而降低对内存的压力。 做法也非常简单,在Activity onDestory时候从view的rootview开始,递归释放所有子view涉及的图片,背景,DrawingCache,监听器等等资源,让Activity成为一个不占资源的空壳,泄露了也不会导致图片资源被持有。

总的来说,我们不是只懂得一些内存泄露解决方法就可以,更重要的是通过日常测试与监控,得到内存泄露检测与修改的一整套闭环体系。

如何降低运行内存的占用

1Android系统何时会发生OOM?

2按照惯例:优化Bitmap占用的内存效果最为明显

说到内存,bitmap必然是这里的大头。对于bitmap内存占用,想说的有以下几点:

一个好的imageLoader,可以将2.X、4.X或5.X对图片加载的处理对使用者隐藏,同时也可以将自适应大小、质量等放于框架中。

3内存占用情况实时监测

对于系统函数onLowMemory等函数是针对整个系统而已的,对于本进程来说,其dalvik内存距离OOM的差值并没有体现,也没有回调函数供我们及时释放内存。假若能有那么一套机制,可以实时监控进程的堆内存使用率,达到设定值即关于通知相关模块进行内存释放,这会大大的降低OOM。 - 实现原理:

4进程隔离

对于webview,图库等,由于存在内存系统泄露或者占用内存过多的问题,我们可以采用单独的进程。微信当前也会把它们放在单独的tools进程中。

5OOM错误信息上报机制

当系统发生OOM的crash时,我们应当上传更加详细的内存相关信息,方便我们定位当时内存的具体情况。其他例如使用large heap、inBitmap、SparseArray、Protobuf等不再一一细述,对代码采用优化--埋坑--优化--埋坑的方式并不推荐。我们应该着力于建立一套合理的框架与监控体系,能及时的发现诸如bitmap过大、像素浪费、内存占用过大、应用OOM等问题。

内存回收(GC)优化

1频繁的GC会带来何种影响

Java拥有GC的机制,不同的系统版本GC的实现可能有比较大的差异。但是无论哪种版本,大量的GC操作则会显著占用帧间隔时间(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了。

2GC的类型介绍

GC的类型有以下几种,其中GC_FOR_ALLOC是同步方式进行,对应用帧率的影响最大。 - GC_FOR_ALLOC: 当堆内存不够的时候容易被触发,尤其是new一个对象的时候,很容易被触发到,所以如果要加速启动,可以提高dalvik.vm.heapstartsize的值,这样在启动过程中可以减少GC_FOR_ALLOC的次数。注意这个触发是以同步的方式进行的。如果GC后仍然没有空间,则堆进行扩张 - GC_EXPLICIT: 这个gc是被可以调用的,比如system.gc, 一般gc线程的优先级比较低,所以这个垃圾回收的过程不一定会马上触发, 千万不要认为调用了system.gc,内存的情况就能有所好转 - GC_CONCURRENT: 当分配的对象大小超过384K时触发,注意这是以异步的方式进行回收的.如果发现大量反复的Concurrent GC出现,说明系统中可能一直有大于384K的对象被分配,而这些往往是一些临时对象,被反复触发了。给到我们的暗示是:对象的复用不够。 - GC_EXTERNAL_ALLOC(在3.0系统之后被废了): Native层的内存分配失败了,这类GC就会被触发。如果GPU的纹理、bitmap、或者java.nio.ByteBuffers的使用没有释放,这种类型的GC往往会被频繁触发。

3内存抖动

Memory Churn内存抖动,内存抖动是因为在短时间内大量的对象被创建又马上被释放。瞬间产生大量的对象会严重占用内存区域,当达到阀值,剩余空间不够的时候,会触发GC从而导致刚产生的对象又很快被回收。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。 通过Memory Monitor,我们可以跟踪整个app的内存变化情况。若短时间发生了多次内存的涨跌,这意味着很有可能发生了内存抖动。

4GC优化方案

通过Heap Viewer,我们可以查看当前内存快照,便于对比分析哪些对象有可能发生了泄漏。更重要的工具是Allocation Tracker,追踪内存对象的类型、堆栈、大小等。手Q有做一个统计工具,对Allocation Tracker的原始数据,按照(类型&堆栈)的组合(堆栈取栈顶的5层)统计某一种对象分配的大小、次数。同时按照次数、大小的排序,从多/大到少/小结合代码分析,并自顶向下的逐轮进行优化。 这样,我们就可以快速知道发生内存抖动时,是因为哪些变量的创建造成频繁GC。一般来说我们需要注意以下几个方面: - 字符串拼接优化:

- 资源重用: 建立全球缓存池,对频繁申请、释放的对象类型重用 - 减少不必要或不合理的对象: 例如在ondraw、getview中应减少对象申请,尽量重用。更多是一些逻辑上的东西,例如循环中不断申请局部变量等 - 选用合理的数据格式: 使用SparseArray, SparseBooleanArray, and LongSparseArray来代替Hashmap

写在最后

我们并不能将内存优化中用到的所有技巧都一一说明,而且随着Android版本的更替,可能很多方法都会变的过时。我在想更重要的是我们能持续的发现问题,精细化的监控,而不是一直处于"哪个有坑填哪里的"的窘况。在这里给大家的建议有:

  • 率先考虑采用已有的工具;中国人喜欢重复造轮子,我们更推荐花精力去优化已有工具,为广大码农做贡献。生活已不易,码农何为为难码农!
  • 不拘泥于点,更重要在于如何建立合理的框架避免发生问题,或者是能及时的发现问题。

当前微信内存监控体系中也存在一些不尽人意的地方,在未来的日子里也同样需要努力去优化。

原文发布于微信公众号 - IT技术精选文摘(ITHK01)

原文发表时间:2018-05-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏cloudskyme

jbpm5.1介绍(4)

eclipse插件 需求 Eclipse的3.6或更新版本 Graphiti框架,使用更新站点 可以直接下载更新Graphiti http://downloa...

3496
来自专栏musazhang的专栏

畅游数据库性能优化过程简析(下)

经过周末两天的折腾,在大家的帮助下最终将用户 DB 的性能峰值由最初的不到 8W 的 QPS + TPS 提升至 17W,心情也由最初的忐忑过渡到现在的平静,现...

3650
来自专栏PHP在线

建立灵巧结构的PHP程序

很早就想写这篇文章了,但一直没有时间完成它。不是说我来告诉大家如何做,我更希望本文只是做为一个引子,与大家来讨论关于如何建立一个有效地、灵活的网络应用程序。 ...

2926
来自专栏Python专栏

Python | 开发者必备的 6 个库

链接:https://www.oschina.net/translate/6-essential-libraries-for-every-python-deve...

1903
来自专栏IT平头哥联盟

webpack4配置详解之常用插件分享

  继上一次 webpack 的基础配置分享之后,本次将分享一些工作中项目常用的配置插件、也会包含一些自己了解过觉得不错的插件,如有分析不到位的,欢迎纠错,嗯,...

1180
来自专栏nnngu

通俗易懂的分析如何用Python实现一只小爬虫,爬取拉勾网的职位信息

源代码:https://github.com/nnngu/LagouSpider

5426
来自专栏nnngu

通俗易懂的分析如何用Python实现一只小爬虫,爬取拉勾网的职位信息

源代码:https://github.com/nnngu/LagouSpider ---- 效果预览 ? 思路 1、首先我们打开拉勾网,并搜索“java”,显示...

3635
来自专栏ATYUN订阅号

开源项目Minio:提供非结构化数据储存服务

Minio是一个在Apache Licence 2.0下发布的对象存储服务器。官网:https://minio.io。它与Amazon S3云存储服务兼容。Mi...

4356
来自专栏北京马哥教育

Python 开发者的 6 个必备库

来自:开源中国 协作翻译 链接: https://www.oschina.net/translate/6-essential-libraries-for-e...

4647
来自专栏IT派

Python 新功能:或将允许安全工具查看运行时操作

针对 Python 编程语言的新功能提议之一是希望为运行时添加“透明度”,并让安全和审计工具查看 Python 何时可能运行潜在危险的操作。

972

扫码关注云+社区