手记

【学习打卡】第4天 App 内存优化

课程名称:Top团队大牛带你玩转Android性能分析与优化
课程章节:App性能概览与平台化实践
主讲老师:随风绽放

课程内容

1.内存问题介绍及优化工具选择

由于 Java 的 JVM 引入了垃圾回收机制,垃圾回收器会自动回收不再使用的对象,使得平时对内存问题投入的关注程度不够。内存问题又是一个大问题,出现内存问题是一个长期累积的结果,需要我们投入更多的关注。

常见的内存问题有三种:

  • 内存抖动,指在短时间内有大量的对象被创建或者被回收的现象,频繁内存抖动会导致垃圾回收频繁运行,造成系统卡顿。
  • 内存泄露,没用的 Java 对象因为还被引用着,导致垃圾回收器没办法移除它们,依旧占用着内存。
  • 内存溢出,App 要使用的内存超过了 JVM 能提供的最大内存,造成 App 崩溃。

常用的内存优化工具也有三种:

  • Memory Profiler,是 AndroidStudio 自带的检测工具,通过实时的图表展示了内存的使用量,还提供了捕获堆转储、强制GC以及跟踪内存分配的能力。特点是方便直观,但只能在开发阶段使用,无法带到线上。
  • Memory Analyzer(MAT),强大的Java Heap 分析工具,查找内存泄漏及内存占用情况。没有 MemoryProfiler 直观,但功能更强大,能够生成整体报告和分析问题报告等。也只能在线下使用,通常使用 MemoryProfiler 进行内存问题的初步定位,使用 MAT 详细分析。
  • LeakCanary,自动内存泄漏检测工具,需要手动集成,在线下使用,leakcanary占用内存很大,可以作为前两个工具的补充。

2.Java 和 Android 的内存管理机制

Java 的内存管理

JVM 管理的内存大致包括三种不同类型的内存区域:

  • Permanent Generation space(永久保存区域),永久保存区域主要存放Class(类)和Meta的信息,Class 第一次被 Load 的时候被放入永久保存区域,Class 需要存储的内容主要包括方法和静态属性。
  • Heap space(堆区域),堆区域用来存放 Class 的实例(即对象),对象需要存储的内容主要是非静态属性。每次用 new 创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被 JVM 的垃圾回收机制管理。
  • Java Stacks(Java栈),Java栈主要存储基本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:永久保存区域和堆区域。

Java 内存的回收算法

1.标记清除算法
标记出所有需要回收等对象,统一回收所有被标记的对象,标记清除算法需要标记所有的内存块,效率不高,此外还会产生大量不连续的内存碎片。

2.复制算法,将内存划分为大小相等的两块,一块用完之后,复制存活对象到另一块,清理另一块内存,示意图如下,复制算法实现简单,效率高,只需要对所有内存的1/2进行标记,没有过多碎片,但浪费一半内存空间,代价大。

3.标记-整理算法
标记过程与“标记-清除”算法一样,存活对象往一端进行移动,清理其余内存。标记-整理算法避免标记,清除导致的内存碎片,也没有复制算法的空间浪费问题。

4.分代收集算法
结合多种收集算法优势(将其应用不同生命周期),新生代对象存活率低,使用复制算法,复制一定比例,老年代对象存活率高,使用标记-整理。

Android 的内存管理机制

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 进程。

3.线上内存监控方案

内存泄露会引起内存溢出和内存抖动,线上内存监控的重点是监测内存泄露问题。

常规方案一

可以预先设定 app 的场景,比如当前内存的使用已经占用到单个 app 可用内存的 80%,达到一个高内存使用的状态,这是可以通过 Dump,具体是调用 Debug.dumpHprofData(),将当前的内存信息转化成文件,然后回传生成的文件到后台,开发人员通过 MAT 手动分析文件来定位内存泄露。

由于 Dump 生成的文件是和当前内存中的对象数相关的,生成的文件会很大,导致回传文件的失败率升高,可以采用 Hook 的方式对文件进行裁剪,减小文件的体积。

方案一配合一定的策略,对监控内存泄露有一定的效果,但需要预设可能出现内存泄露的场景,而且需要考虑回传文件失败的可能。

常规方案二

将 LeakCanary 带到线上,LeakCanary 发现内存泄露大致经历下面几个过程:

  1. 监测 Activity 的生命周期的 onDestroy() 的调用;
  2. 当某个 Activity 的 onDestroy() 调用后,便对这个 Activity 创建一个带 ReferenceQueue 的弱引用,并且给这个弱引用创建了一个 key 保存在 Set集合中;
  3. 如果这个 Activity 可以被回收,那么弱引用就会被添加到 ReferenceQueue 中。
  4. 等待主线程进入 idle (即空闲)后,通过一次遍历,在 ReferenceQueue 中的弱引用所对应的 key 将从 RetainedKeys 中移除,说明其没有内存泄漏。
  5. 如果 Activity 没有被回收,先强制进行一次 GC,再来检查,如果 key 还存在 RetainedKeys 中,说明 Activity 不可回收,同时也说明了出现了内存泄漏。
  6. 发生内存泄露之后,通过 Dump 内存快照,生成 .hprof 文件。

由于 LeakCanary 的分析内存泄露的路径比较长,分析过程中占用内存比较大。我们可以对 LeakCanary 进行定制,只分析Retain size 大的对象,不是所有的都进行分析。对 Dump 生成的内存快照进行裁剪,而是全部加到内存。

通过以上两种方案的介绍,我们可以指定一套完整的线上内存监控方案。对常规内存指标的监测,包括待机内存、重点模块内存占用、OOM率等,此外还要监控 App 一个完整生命周期和重点模块的 GC 次数和 GC 时间。通过定制化的 LeakCanary 来定位和分析内存泄露。

课程收获

这一章内容介绍了内存抖动、内存泄露和内存溢出三种常见的内存使用问题。其中内存泄露是我们需要关注的重点。这一章还详细介绍了三种内存使用分析工具,并给出了一套适合线上的内存监控方案。



0人推荐
随时随地看视频
慕课网APP