Flutter 使用 Dart 作为开发语言和运行库。具有调试模式和发布模式这两种构建模式,并且它们之间存在着巨大的差异。
在调试模式下,Dart的大部分管道都被传输到设备上:Dart运行时、即时编译器/解释器(JIT用于Android,解释器用于iOS)、调试和分析服务。
在发布模式下,JIT/解释器 和 调试服务 被剥离,但 Dart运行时 仍然存在,这是影响 Flutter 应用程序基本大小的主要因素。
Dart运行时 包括一个垃圾收集器,它是当对象被实例化并变得不可访问时,分配和释放内存所必需的组件。
Flutter 可能有大量的对象。无状态小部件在屏幕上呈现时创建,当应用程序的状态发生变化或不再可见时销毁和重建,大多数小部件的使用寿命很短。对于具有相当复杂UI的应用程序,它可以运行数千个小部件。
那么,Flutter的开发人员是否应该害怕担忧对象收集呢?对于频繁创建和破坏对象的Flutter,开发人员是否应该采取措施限制这种行为?一些新的Flutter开发人员在State中创建不会随时间变化的小部件的引用,这样它们就不会被破坏和重建。不要这样做。
担忧Dart的垃圾收集在很大程度上是没有根据的,因为它有分代体系结构和一个为快速创建和销毁对象而优化的实现。在大多数情况下,您应该让Flutter的引擎创建并销毁它选择的所有小部件。
Dart 垃圾收集器
Dart的垃圾收集包括两个阶段:年轻的空间清除器(young space scavenger)和并行标记清除器(parallel mark sweep collectors)。
调度
为了将垃圾收集对应用程序和UI性能的影响最小化,垃圾收集器提供了Flutter 引擎的钩子,当Flutter 引擎检测到应用程序空闲且没有用户交互时,会向它发出警报。这使垃圾收集器有机会在不影响性能的情况下运行其收集阶段。
垃圾收集器还可以在这些空闲时间间隔内运行内存行滑动压缩(sliding compaction),从而通过减少内存碎片将内存开销降至最低。
年轻的空间清除器(young space scavenger)
此阶段旨在清理生命周期较短的临时对象,如无状态小部件。虽然它是阻塞的,但它比第二阶段 并行标记清除器(parallel mark sweep collectors) 快得多,并且当与以上 调度 相结合时,几乎消除了运行时应用程序中感知到的暂停。
本质上,对象被分配到内存中的一个连续空间,当对象被创建时,它们被分配到下一个可用空间,直到分配的内存被填满。Dart使用 bump pointer 快速分配新的空间,使进程非常快。
分配对象的空间由两部分组成,称为 semi 空间。任何时候都只使用一半:一半处于活跃状态的空间,另一个处于非活跃状态的空间。在活跃状态的空间中分配新对象,填满后,活跃对象将从活跃空间复制到非活跃空间,忽略死对象。然后,非活跃空间变为活跃,并且该过程重复。
为了确定哪些对象是活的或死的,收集器从根对象(如堆栈变量)开始,并检查它们引用的对象。然后移动引用的对象。它检查这些疏散的对象指向什么,并移动这些引用的对象。这个过程一直持续到所有活跃对象都被移动为止。死对象没有引用,因此会被留下。活跃对象将在未来的垃圾收集事件中被复制。
有关这方面的更多信息,请查看切尼的算法。
并行标记清除器(parallel mark sweep collectors)
当对象达到一定的持续时间后,它们被提升到一个新的内存空间,由第二阶段收集器管理:标记-扫描。
这种垃圾收集技术有两个布:首先遍历对象图,然后标记仍在使用的对象。在第二个阶段,将扫描整个内存,并回收任何未标记的对象。然后清除所有标志。
这种形式的垃圾收集块在标记阶段;内存不会发生变化,UI线程会被阻塞。这种收集比较少见,因为短寿命对象是由 年轻的空间清除器(young space scavenger) 处理的,但是有时 Dart 运行时需要暂停,以便运行这种形式的垃圾收集。考虑到 Flutter 调度能力,这种影响应该降到最低。
需要注意的是,如果应用程序不符合弱分代假说(Weak Generational Hypothesis)这说明大多数对象在年轻时死亡,那么这种形式的收集将会更频繁地出现。考虑到Flutter的小部件实现的工作方式,这是不太可能的,但要记住一些事情。
Isolates
值得注意的是,Dart isolates 有它们自己的私有堆,彼此独立。由于每个 Isolate 在单独的线程中运行,每个 Isolate 的垃圾收集事件不应该影响其他Isolate的性能。使用Isolate是避免阻塞UI和卸载流程密集型活动的好方法。
总结
现在你就知道了:Dart使用了一个强大的分阶段垃圾收集器,以最大限度地减少在Flutter应用程序中缓解垃圾收集的影响。所有不要担忧垃圾收集器。