1. 介绍
Flutter 主要使用 Dart 开发语言, 在调试和发布两个版本中, Dart RunTime 是始终存在, 但两种版本下的构建方式有很大的差异
2. 调试和发布版本下的差异
调试版本下 Dart 编译到设备, 包含三部分:
- Dart RunTime
- jit(Android 下的实时编译器)/interpreter(iOS 下的解析器)
3. 调试和分析服务
发布版本下
1.Dart RunTime
两种模式下都存在 Dart RunTime, 它包含了垃圾收集器, 是实例化对象并变得无法访问时分配和释放内存的必要组件.
3. 垃圾收集器竞技场
对于 Flutter 而言, 会创建很多对象: 例如 Stateless Widget 从创建到应用程序的状态发生改变或者变得不再可见时被销毁和重建, 大多数对象的生命周期是短暂的, 若应用程序的 UI 变得相对复杂, 可运行至上千个小部件
对于上面而言, 很多人之前认为 Flutter 为什么不用 Java 写, 为什么不用 Object-C 写, 为什么不用 JavaScript 写, 对于这些语言真的能胜任这么频繁的创建销毁吗?
Java 垃圾收集器
jvm 中 java 的内存分为四个部分:
1.Java 栈: 主要作用存放方法执行的时候所有的数据, 由栈帧代表一个方法的执行, 每个方法从调用到执行完成在虚拟机为一个栈帧的入栈和出栈, 栈帧的信息包括局部变量表, 栈操作数, 动态链接, 方法出口
2. 本地方法栈: 主要为 native 服务, 例如 C,C++ 方法
3. 方法区: 存储被虚拟机加载的类信息, 常量, 静态变量, 即使编译器编译后的数据等
4. 堆区: 所有通过 new 创建的对象的内存都在堆中分配, 堆内存分为新的和旧的, 刚 new 出来的对象放在新生代存储, 当内存不足时, 虚拟机会通过一系列算法把新生对象移动到旧生代中去
注意:
1. 当方法栈深度大于 JVM 深度的时候, 就会栈溢出, 例如: 死循环 (Stack Overflow)
2. 新生代和旧生代都满了, 就会导致内存溢出 (OutOfMemory)
垃圾收集器的算法
垃圾回收主要针对堆内存, 算法主要包括垃圾的确定与收集, 垃圾的回收, 垃圾的回收时机
1. 引用计数法 (废弃): 若对象被引用就会 + 1, 没有被引用的时候就回收, 但引用计数法无法解决对象之间相互调用的问题
2. 可达性算法: 通过 gc root 对象开始搜索, 不可达的对象会被回收, 引用的类型主要有强引用, 弱引用, 当存在强引用时宁愿抛出 oom 也不回收, 但是弱引用的话, 有可能被回收.
3. 标记清除法: 搜索发现没有引用的对象直接回收, 但是导致碎片过多
4. 复制算法: 搜索扫描没有引用的对象, 开辟新的内存空间, 将存活的对象复制到新的内存, 旧的内存直接删除, 由于交换空间, 适合对象比较少的时候, 并且内存空间缩短一半
5. 标记整理法: 在标记清除法的基础上, 清除掉不存活的对象, 把后面存活的对象挪动过来, 解决碎片问题
上面的垃圾收集器算法在 jvm 中没有明确的规范, 由各个厂商去实现
Object-C 垃圾收集器
OC 在早期版本中缺少较为完善的内存管理机制, 需要开发者手动进行释放, 在 Xcode4.2 之后引入了 ARC(Automatic Reference Counting) 机制.
ARC 机制
ARC 叫做自动引用计数, ARC 中常见的所有权关键字:
assign 对应关键字
__unsafe_unretained
, 指向的对象被释放的时候, 仍然指向之前的地址, 容易引起野指针
copy 对应关键字__strong, 在赋值的时候, 调用 copy 方法
retain 对应关键字__strong
strong 对应关键字__strong
unsafe_unretained 对应关键字 unsafe_unretained
weak 对应关键字 weak
ARC 内部实现
ARC 背后的引用计数主要依赖于三个方法:
retain 增加引用计数
release 降低引用计数, 当引用计数为 0 时释放对象
autorelease 在当前
的 auto release pool
结束后, 降低引用计数
JavaScript 垃圾收集器
JavaScript 具有垃圾自动收集机制, 垃圾收集器会按照固定的时间间隔, 周期性地执行这一炒作, 具体到浏览器的实现, 也可以指定收集时间
垃圾收集的方法
标记清除法 JavaScript 中最重要的收集方法, 给当前不使用的值加上标记, 然后等待回收其内存
引用计数 (不再使用)
性能问题 垃圾收集器是周期运行的, 而且如果变量分配的内存数量比较大, 那么回收工作量也是相当的大
Dart 垃圾收集器
Dart 的垃圾收集器是分代的, 由两个部分组成: 新生代空间收集器, 并行标记扫描收集器, 还有一个重要的东西, 就是调度器
调度器
在 Flutter 引擎中, 为了最小化垃圾收集对应用程序和 UI 性能的印象, 与垃圾收集器提供了 hook, 当引擎检测到应用程序处于空闲状态 (没有与用户交互), 会发出警报, 为垃圾收集器提供运行其收集阶段而不影响性能的机会. 并且垃圾收集器可以在这些空闲时间运行内存压缩, 从而较少内存碎片来优化内存
新生代空间收集器
此部分类似于 Java 的复制算法, 用于清理寿命较短的对象, 例如 Stateless 部件, 虽然是会阻塞线程, 但当与调度器结合使用, 几乎感知不到应用程序在运行期间的暂停, 从本质上, 新建的对象被分配给内存中的连续空间, 在新建对象, 会被分配到下一个可用空间, 直到填充完分配的内存, 但 Dart 使用的是一个凹凸的指针, 所以这个过程非常快, 分配新对象的空间由两部分组成, 任何时候只用一半, 当一半满后, 活动的对象将复制到另一半空间中, 一半就会全部清空, 确定对象是否活动, 收集器以根对象开始, 进行检测他们引用的内容, 这一部分类似于 Java 的可达性算法, 有引用的对象将会被复制到另一个空间中
并行标记扫描收集器
当对象达到一定的生命周期时, 会被提上到另一个新的内存空间, 由另一个收集器管理, 此收集器有两个阶段:
遍历对象, 标记仍在使用的对象
扫描整个存储器, 并回收未标记的对象, 然后清除所有标记
4. 总结
由上面所述, Dart 的垃圾收集器方式参考了部分语言的实现, 但需要注意的是, Dart 的 isolates 拥有自己的私有堆, 彼此是独立的, 每个 isolates 运行在单独的线程中, 每个 ioslates 的垃圾收集事件不影响其它 isolates 的性能, 所以 isolates 可以避免 UI 出现卡顿和很好的进行频繁的回收操作, 这就是 dart 作为 Flutter 的主要语言的原因之一.
参考: 1.Flutter: Don't Fear the Garbage Collector
来源: https://juejin.im/post/5c939f98f265da612d633c4d