浅谈 Flutter 内存分析


Profile 模式

分析内存性能时,是在 profile 模式下,使用

flutter run --profile

或者是 VSCode 的 launch.json 配置添加一个 flutterMode: 'profile' ,profile 模式主要有以下特点:

  1. profile 模式无法在模拟器下运行,需要使用真机,因为需要更贴近实际生产环境的使用情况
  2. 开启了一些服务扩展,例如 Performance overlay 性能监控工具
  3. 跟踪调试也是可以用的,支持源码级别的 debug,也就是代码并没有压缩和混淆
  4. profile 模式下不支持平时开发时使用的重启应用功能,也没有 hot reload

启动 DevTools

内存的具体情况可以使用 Flutter 提供的 DevTools 中的内存视图来跟踪调试,在 VSCode 中直接使用 Command + p 使用 Dart: Open DevTools 来启动。

DevTools 有很多功能,只关注内存视图先,如果是其他的 IDE,参考官网文档来开启:

DevTools

基础概念

官方文档提供的图解,我们一一研究下具体是什么东西。

/memory/0.png

X 轴是时间轴,Y 轴是内存使用数量轴,各种标识的意义可以打开右上角的 Legend 看看简单说明。

中间一大片蓝色区域有几个概念:

  • 最顶部的虚线,是 RSS(resident set size),即常驻内存大小,可以理解为分配给进程的所有内存用量,不包括交换出去的虚拟内容,包括共享类库的内容,栈和堆占用的内容
  • 在第二高位置更淡一点的虚线,是 Heap Capacity,即堆容量
  • 然后是深色一点的蓝色部分,是 Used Heap,即 Dart 里边 objects 占用的内存部分,在 Dart 里,理论上所有数据都是 object
  • 接着是浅色一点的蓝色部分,是 External Heap,不在 Dart 主要 heap 中的部分,主要是原生系统级别的一些对象,例如内存中的文件内容或图片等,Dart 会创建一种包装来操作这些原生的资源。

更多关于 Flutter engine embedder 的内容可以参考:flutter/flutter

操作和事件

在时间轴上边会有很多小点点,分别来看他们是什么:

  • 蓝色实心点,是 VM GC 触发的时机
  • 红色圈圈,自动快照的时机,当内存占用突然升高搞过之前百分之四十以上时系统会主动触发快照,便于开发者排查对应问题
  • 黄色圈圈,一次内存分配监控的操作,具体下边展开讲
  • 黄色实心点,一次重置内存分配增量的操作
  • 蓝色圈圈,用户触发 GC,只是建议 VM 进行 GC,大多数情况下会和蓝色实心点一起出现
  • 绿色圈圈,用户触发快照

黄色圈圈和黄色实心点对应下边的这两个操作按钮:

/memory/1.png

第一个是开始监控内存的分配情况,第二个是重置内存分配情况的增量数据。我们实际操作一遍就能够理解这两个功能的作用。

先启动 APP,打开 DevTools 的内存视图,在一个时间点点击开始监控内存分配,有以下结果:

/memory/2.png

左侧是监控的时间点,点击后可以展开右侧更详细的内容,Class 即我们代码中的各个类,Instances 是实例数量,随后的 Delta 是其增量,Bytes 是内存分配量,随后的 Delta 也是其增量。

第一次我们使用监控时,你可以发现 Delta 都和数量一样的,所以这就是从 app 一启动到你监控时间点的增量,增加了多少,现在就有多少。

然后我们点击重置增量,你可以发现所有的 Delta 都为 0 了,然后点击一个按钮(在代码里我这个点击操作创建了大量的 SpecialClass 实例),再次点击监控,会发现右侧数据发生了变化,然后点击每一项顶部可以调整排序(通常我们需要最多到最小的排序),然后你可以看到,SpecialClass 的实例增加了多少,内存用量增加了多少。

/memory/3.png

所以,这个工具可以很方便地查出在某段时间内内存分配的增量变化以及其对应的类,可以有效地帮助我们排查内存泄露相关问题。

快照

如下图,红色圈出来的是手动保存快照的按钮

/memory/4.png

保存了快照后可以在下方的列表查看快照的详细信息。

每一个快照会生成一个对应的分析内容,也是在下方的列表中,通过时间点匹配。

/memory/5.png

如上图,快照中有多个节点,分别的内容是:

  • External 就是我们上边提到的 External 那部分内容的详细信息
  • Filtered 根据包名筛选好的分组
  • package 是你应用包里的内存占用信息
  • src 这个不确定,看上去像是 Dart 基础类的东西

我们可以找到你想查看的相关实例,去看实例的具体数据,例如:

/memory/6.png

上图可以看到 MyHomePage 这个类对应的一个实例中的 title 属性值为 Flutter Demo Home Page

接下来我们来看每次快照对应生成的分析内容,这个内容是官方觉得比较容易出问题那一部分内容的分析情况,例如图片,加载长列表,加载大文件等。

/memory/7.png

这个 Analysis 会把所有这些容易出问题部分的类都收集到一起便于你排查,如上图 Image 和 ImageInfo,以及图片在内存中的表示,_Int32List(一些新手机会是 _Int64List)的大致情况都列出来了。

通过这个 Analysis 可以帮助我们快速找到那些对象占用了大量的内存。

实践例子

teabyii/flutter_memory_view

简单的 Demo 操作起来很简单,但是实际的分析情况确会很复杂。

Dart VM GC

Flutter: Don’t Fear the Garbage Collector

这个文章内容其实十分精简,最主要向你传递一个信息,Flutter 引擎配置 Dart VM 的 GC 很强大,你不用顾虑说使用 Flutter 时会创建大量的 Widget。

Android Flutter 内存初探

Dart VM 将内存管理划分为两块:New Generation 和 Old Generation

New Generation 存放内存较小并且生命周期较短的对象,会频繁进行 GC,例如 stateless widget,使用 Cheney 算法

/memory/8.png

New Generation 中幸存下来的家伙会被放到 Old Generation 中,Old Generation 存放生命周期长,内存用量大的对象。

Old Generation 中使用 mark-sweep 的方式来进行 GC,分为两个阶段:

  • 遍历对象图,标记仍在使用的对象
  • 扫描整个存储器,回收未标记的任何对象,然后清除标记

标记阶段,该线程中的内存区域是不可修改的,清除阶段则有专门的 GC 线程处理,不影响其他的应用线程。

/memory/9.png

后续可以再进一步了解的内容有:

  • DevTools 更多的功能,例如 performance,timeline 等
  • Android 和 iOS 原生侧的内存分析

相关资料