课程名称:Top团队大牛带你玩转Android性能分析与优化
课程章节:App性能概览与平台化实践
主讲老师:随风绽放
由于 Java 的 JVM 引入了垃圾回收机制,垃圾回收器会自动回收不再使用的对象,使得平时对内存问题投入的关注程度不够。内存问题又是一个大问题,出现内存问题是一个长期累积的结果,需要我们投入更多的关注。
常见的内存问题有三种:
常用的内存优化工具也有三种:
JVM 管理的内存大致包括三种不同类型的内存区域:
1.标记清除算法
标记出所有需要回收等对象,统一回收所有被标记的对象,标记清除算法需要标记所有的内存块,效率不高,此外还会产生大量不连续的内存碎片。
2.复制算法,将内存划分为大小相等的两块,一块用完之后,复制存活对象到另一块,清理另一块内存,示意图如下,复制算法实现简单,效率高,只需要对所有内存的1/2进行标记,没有过多碎片,但浪费一半内存空间,代价大。
3.标记-整理算法
标记过程与“标记-清除”算法一样,存活对象往一端进行移动,清理其余内存。标记-整理算法避免标记,清除导致的内存碎片,也没有复制算法的空间浪费问题。
4.分代收集算法
结合多种收集算法优势(将其应用不同生命周期),新生代对象存活率低,使用复制算法,复制一定比例,老年代对象存活率高,使用标记-整理。
Android 的内存采用弹性分配的原则,内存的分配值和最大值收到具体设备的影响。通常发生内存溢出(OOM)有两种情况,一种是设备本身的内存不够,导致 app 分配不到使用所需的内存。另一种情况是启动了多个 app,使得系统的可用内存不足。
Android 并没有直接使用 Java 的 JVM,而是有自己的一套虚拟机。在 Android5.0 之前是 Dalvik,Android5.0 之后是 Art。Dalvik仅固定一种回收算法,Art回收算法可运行期选择,比如App 在前台时,对响应速度要求高,可能会使用标记-清除算法。App在后台时,使用标记-整理算法。此外 Art 还具备内存整理能力,减少碎片的产生。
最后 Android 还有一套 Low Memory Killer机制,Android 将进程按照优先级高低依次划分为前台进程、可见进程、服务进程、空进程。前面优先级最高,后面最低,当内存不足时,优先回收低优先级进程。在回收内存时,还会考虑回收的收益,比如回收进程 A 可以得到 300M 的内存,回收进程 B 可以得到 10K 的进程,这是就会考虑回收 A 进程。
内存泄露会引起内存溢出和内存抖动,线上内存监控的重点是监测内存泄露问题。
可以预先设定 app 的场景,比如当前内存的使用已经占用到单个 app 可用内存的 80%,达到一个高内存使用的状态,这是可以通过 Dump,具体是调用 Debug.dumpHprofData(),将当前的内存信息转化成文件,然后回传生成的文件到后台,开发人员通过 MAT 手动分析文件来定位内存泄露。
由于 Dump 生成的文件是和当前内存中的对象数相关的,生成的文件会很大,导致回传文件的失败率升高,可以采用 Hook 的方式对文件进行裁剪,减小文件的体积。
方案一配合一定的策略,对监控内存泄露有一定的效果,但需要预设可能出现内存泄露的场景,而且需要考虑回传文件失败的可能。
将 LeakCanary 带到线上,LeakCanary 发现内存泄露大致经历下面几个过程:
由于 LeakCanary 的分析内存泄露的路径比较长,分析过程中占用内存比较大。我们可以对 LeakCanary 进行定制,只分析Retain size 大的对象,不是所有的都进行分析。对 Dump 生成的内存快照进行裁剪,而是全部加到内存。
通过以上两种方案的介绍,我们可以指定一套完整的线上内存监控方案。对常规内存指标的监测,包括待机内存、重点模块内存占用、OOM率等,此外还要监控 App 一个完整生命周期和重点模块的 GC 次数和 GC 时间。通过定制化的 LeakCanary 来定位和分析内存泄露。
这一章内容介绍了内存抖动、内存泄露和内存溢出三种常见的内存使用问题。其中内存泄露是我们需要关注的重点。这一章还详细介绍了三种内存使用分析工具,并给出了一套适合线上的内存监控方案。