Android内存优化方向探讨

本文转自内存优化方向探讨html

前言

最近复习对以往项目中和学习中一些内存优化的方法进行下探讨和汇总. 虽然如今Android设备的内存愈来愈大,4GB内存手机已经至关普片了,可是只有有效的控制好内存才能将你的应用性能发挥到极致.因此控制好内存如今依然是Android应用中一个重要的技术指标.android

开篇以前咱们先说说内存.内存已是咱们很熟悉的概念了,可是你能说出手机内存和电脑内存的区别吗?手机内存之间有差别吗?内存是否大就好?git

  • 什么是手机内存?

从本质上看二者是相同的,电脑上用的是DDR内存,而移动设备上的是LPDDR内存.LPDDR的英文全称为Low Power Double Data Rate,LP即为低功耗,专门用于移动式的电子产品。相较于PC领域的DDR4内存,LPDDR4形态的内存更省电.程序员

  • 手机内存的差别?

如今主流的运行内存分别有LPDDR三、LPDDR4以及LPDDR4X.github

从上图能够看出LPDDR4的性能要比LPDDR3高出一 倍,而LPDDR4X相比LPDDR4工做电压更低,因此也比LPDDR4省电20%-40%。固然图中的数据是标准数据,不一样的生成 厂商会有一些低频或者高频的版本,性能方面高频要好于低频.

  • 手机内存是否大就好?

其实从上面一点咱们就能得出结论. 若是一个用8GB LPDDR4X的手机性能通常是优于 12GB LPDDR3手机的. 可是内存并非一个孤立的概念,它跟操做系统、应用生态这些因素都有关. 相信你们也对几年前的IPhone6 1GB的运行内存就能吊打4-6GB的Android手机还印象深入.一样的内存,AndroidQ系统性能就会比Android4.0好.封闭规范的IOS系统就会比开放的Android系统好.web

常见误区

通常你们对手机内存都有一个比较粗略的概念,就是分堆和栈,但实际上仍是复杂得多. 算法

主要包括程序计数器PC、虚拟机栈、本地方法栈、Java堆、方法区、常量池等,这里推荐你们能够看下 详解内存优化的前因后果一文,有详细的介绍.

这里我只说下两个容易陷入的误区数组

  • 对象只要小于内存空间便可加载?

我曾经遇到一个问题,在加载一个5MB的图片到界面时忘记压缩出现了OOM,但当我查看日志时发现可用堆内存明明还大于5MB,为何就OOM呢? 缓存


后面我知道了堆内存其实还分新生代、老年代、永久代,各会占据必定空间如上图, 日志里看到的了大于5MB实际上是新老内存共同的结果.当遇到超大对象 时,会直接将超大对象跳过新生代放置到老年代中.实际上老年代空间里已经不足5MB空间了.就算老年代空间足够若是GC是 CMS(每一个厂商实现略有不一样,通常是混合的算法),那么只会标记清理,并不会压缩,因此内存会碎片化,同时可能出现浮游垃圾.即便老年代空间大于5MB,也会出现 没有 连续空间供该对象使用.
PS:系统给每一个应用分配的空间是有限的.当内存达到必定的 阀值也会报OOM.
因此即便内存空间足够也要注意,大资源的使用.

  • 那内存越少越好?

这也是很多人有的误区之一,甚至有人当内存是洪水猛兽,用得越少越好,这样容易优化过分产生其余问题.如今手机的发展内存愈来愈多,若是只有一个具体的数值,作硬指标实际上是没有必要的.APP是否占用过多的内存,跟设备、性能、和当时的状况也有关.可让高端设备使用更多的内存,作到针对设备性能的好坏使用不一样的内存分配和回收策略.bash

内存优化方向

  • 内存分级优化

要作到针对设备进行优化处理,须要一个良好的架构支撑,架构须要支持如下几点:

  1. 设备分级: 使用相似device-year-class的策略对设备分级,对于低端机用户能够关闭复杂的动画,或者是某些功能;使用 565格式的图片,使用更小的缓存内存等。在现实环境下,不是每一个用户的设备都跟咱们的测试机同样高端,在开发过程我 们要学会思考功能要不要对低端机开启、在系统资源吃紧的时候能不能作降级

  2. 缓存管理:咱们须要有一套统一的缓存管理机制,能够适当地使用内存;当"系统有难"时,也要责无旁贷地归还.咱们能够 使用OnTrimMemory回调,根据不一样的状态决定释放多少内存.对于大项目来讲,可能存在几十上百个模块,统一缓存管 理能够更好地监控每一个模块的缓存大小.

  3. 进程模型:一个空的进程也会占用10MB的内存,而有些应用启动就有十几个进程,甚至有些应用已经从双进程保活升级到 四进程保活,因此减小应用启动的进程数、减小常驻进程、节制的保活,对低端机内存优化很是重要.

  4. 安装包大小: 安装包中的代码、资源、图片以及so库的体积,跟它们占用的内存有很大的关系.一个80MB的应用很难在 512MB内存的手机上流畅运行.这种状况咱们须要考虑针对低端机用户推出轻量版本,例如QQ音乐HD、快手极速版、今日头条极速版都是这个思路.

  • Bitmap优化

图片资源内存通常占APP总内存很大一部分,因此作内存优化永远没法避开图片内存这个"永恒主题". 即便把全部的Bitmap都放到Native内存,并不表明图片内存问题就彻底解决了,这样作只是提高了系统内存利用率,减小了 GC带来的一些问题而已. 那咱们该如何优化图片内存呢?我推荐如下两种方法:

  1. 统一图片库

图片内存优化的前提是收拢图片的调用,这样咱们能够作总体的控制策略.例如低端机使用565格式、更加严格的缩放算法, 可使用Glide、Fresco或者采起自研均可以.并且须要进一步将全部Bitmap.createBitmap、BitmapFactory相关的接口也一并收拢.

  1. 统一监控

在统一图片库后就很是容易监控Bitmap的使用状况了,这里主要有两点须要注意.

  • 大图片监控: 咱们须要注意某张图片内存占用是否过大,例如⻓宽远远大于View甚至是屏幕的⻓宽。在开发过程当中,若是 检测到不合规的图片使用,应该当即弹出对话框提示图片所在的Activity和堆栈,更快发现并解决问题.在灰度 和线上环境下能够将异常信息上报到后台,咱们能够计算有多少比例的图片会超过屏幕的大小,也就是图片的"超宽率".
  • 分析图片总内存: 经过收拢图片使用,咱们还能够统计应用全部图片占用的内存,这样在线上就能够按不一样的系统、屏幕分辨 率等维度去分析图片内存的占用状况.在OOM崩溃的时候,也能够把图片占用的总内存、Top N图片的内存都写到崩溃日志中,帮助咱们排查问题.

讲完设备分级和Bitmap优化,咱们发现架构和监控须要两手抓,一个好的架构能够减小甚至避免咱们犯错,而一个好的监控 能够帮助咱们及时发现问题.

  • 内存泄漏

内存泄漏简单理解就是没有及时的回收不用的内存,排查和解决内存泄漏也是内存优化没法避开的工做之一.
内存泄漏主要分两种状况,一种是同一个对象泄漏,还有一种状况更加糟糕,就是每次都会泄漏新的对象,可能会出现几百上 千个无用的对象.
不少内存泄漏都是框架设计不合理所致使,各类各样的单例满天⻜,MVC中Controller的生命周期远远大于View.优秀的框架 设计能够减小甚至避免程序员犯错,固然这不是一件容易的事情,因此咱们还须要对内存泄漏创建持续的监控.

1. Java内存泄漏: 创建相似LeakCanary自动化检测方案,至少作到Activity和Fragment的泄漏检测.在开发过程,咱们但愿 出现泄漏时能够弹出对话框,让开发者更加容易去发现和解决问题.内存泄漏监控放到线上并不容易,咱们能够对生成的 Hprof内存快照文件作一些优化,裁剪大部分图片对应的byte数组减小文件大小。好比一个100MB的文件裁剪后通常只剩下 30MB左右,使用7zip压缩最后小于10MB,增长了文件上传的成功率

2. OOM监控: 美团有一个Android内存泄露自动化链路分析组件Probe,它在发生OOM的时候生成Hprof内存快照,而后经过 单独进程对这个文件作进一步的分析.不过在线上使用这个工具⻛险仍是比较大,在崩溃的时候生成内存快照有可能会导 致二次崩溃,并且部分手机生成Hprof快照可能会耗时几分钟,这对用户形成的体验影响会比较大.另外,部分OOM是由于 虚拟内存不足致使,这块须要具体问题具体分析.

3.Native内存泄漏监控: 在 WeMobileDev的一篇文章《微信Android终端内存优化实践》中,微信也作了一些监控方案上面的尝试.

4. 针对没法重编so的状况: 使用了PLT Hook拦截库的内存分配函数,其中PLT Hook是Native Hook的一种方案.而后重定向到咱们本身的实现后记录分配的内存地址、大小、来源so库路径等信息,按期扫描分配与释放是否配 对,对于不配对的分配输出咱们记录的信息。

5. 针对可重编的so状况: 经过GCC的"-finstrument-functions"参数给全部函数插桩,桩中模拟调用栈入栈出栈操做;经过ld 的“–wrap”参数拦截内存分配和释放函数,重定向到咱们本身的实现后记录分配的内存地址、大小、来源so以及插桩记录的 调用栈此刻的内容,按期扫描分配与释放是否配对,对于不配对的分配输出咱们记录的信息.

  • GC监控

在实验室或者内部试用环境,咱们也能够经过Debug.startAllocCounting来监控Java内存分配和GC的状况,须要注意的是这个 选项对性能有必定的影响,虽然目前还可使用,但已经被Android标记为deprecated. 经过监控,咱们能够拿到内存分配的次数和大小,以及GC发起次数等信息.

long allocCount = Debug.getGlobalAllocCount(); 
long allocSize = Debug.getGlobalAllocSize();
long gcCount = Debug.getGlobalGcInvocationCount();
复制代码

上面的这些信息彷佛不太容易定位问题,在Android 6.0以后系统能够拿到更加精准的GC信息.

//运行的GC次数 
Debug.getRuntimeStat("art.gc.gc-count");
//GC使用的总耗时,单位是毫秒
Debug.getRuntimeStat("art.gc.gc-time");
//阻塞式GC的次数 
Debug.getRuntimeStat("art.gc.blocking-gc-count");
//阻塞式GC的总耗时 
Debug.getRuntimeStat("art.gc.blocking-gc-time");
复制代码

须要特别注意阻塞式GC的次数和耗时,由于它会暂停应用线程,可能致使应用发生卡顿。咱们也能够更加细粒度地分应用场景统计,如登录、启动、主业务等关键场景.

常见优化方式

常见的优化方式 如handler、webview、内存抖动、匿名内存类等等网上还有不少就不一一介绍了.建议参考Android内存优化全解析探索 Android 内存优化方法

总结

在进行内存的优化前,咱们首先搞清楚要优化的程度?内存产生了什么异常和卡顿? 只有在明确了APP现状和优化目标后,咱们才能去进行下一步的操做.在探讨了内存优化的思路时,针对不一样的设备、设备不一样的状况,咱们但愿能够给用户不一样的体验.这里我主要讲到了关于 Bitmap内存优化和内存泄漏排查、监控的一些方法.但愿对你们有所帮助.

参考文章

Manage Your App's Memory

为何内存使用不多的时候也GC

一文解决内存屏障

leakcanary

相关文章
相关标签/搜索