Flutter使用dart
语言作为其开发语言和运行环境。dart
的runtime
是一直存在的,但是在debug
和release
模式下有一些区别。
- 在
debug
模式下,dart
大部分组件都放在设备上,例如runtime
、JIT(Android)
、interpreter(iOS)
、debug
和profile
services
。 - 在
release
模式下,只剩下runtime
,而这也是Flutter App能够运行起来的最基本组件。
在runtime
中,存在一个在初始化对象时为其分配内存,对象不再被使用的时候回收内存的组件,即GC。
在Flutter
中存在很多对象。以Stateless
Widget
为例,其在State
发生变化或者Widget
不可见的时候不断地发生重建和销毁(注意,此处是指Widget
树中的Widget
,对于Element
树和RenderObject
树来说,element
和renderObject
是可变的,而且其初始化生成需要消耗很多资源。因此在大多数情况下他们是会被回收利用的)。这些Widget
的生命周期都很短,对于一个UI比较复杂的APP来说,可能会有数千个Widget
需要被经常回收创建。
所以有些开发者可能会采取一些措施来避免太过频繁的GC。比如为了保持一个引用的Widget
对象不会被回收,将其放在state
中(这样并不是说真的不会被回收,只是创建回收的频率被降低了,因为state
是属于element
的,而element
的生命周期是比较长的)。
这么做是没有必要的,首先Widget
是一个很轻量级的对象,它的创建和回收并不会占用很多资源,真正占用资源的是Element
和RenderObject
。其次dart
的GC机制能够快速有效的进行对象回收,不用担心Widget
创建过多导致OOM
出现。
关于Widget
、Element
、RenderObject
的更多关系请参看这篇文章Flutter中的层级蛋糕。
Dart GC
和Jvm类似,dart中的GC是分代的,一个是年轻代,一个是老年代。如果熟悉Jvm内存机制的童鞋可以快速略过这一部分,直接看最下面的结论。
1. schedule
首先介绍下dart
中的调度机制:为了最小化GC对APP和UI的性能影响(因为dart的GC有一种类似于JVM中stop the world
的机制,导致APP对事件无响应、UI无法刷新),GC通过与Flutter
的engine
建立联系,在Flutter APP处于空闲、无用户交互、或者在后台的情况下,engine通知GC进行回收工作。这样就不会对APP和UI产生影响了。
同时GC还会使用滑动压缩(类似于JVM中标记整理中的整理)的方法来减少内存碎片的数量,从而减少内存开销。
2. 年轻代
dart
内存中的年轻代和JVM中的年轻代很相似。
年轻代上存放的对象是那些生命周期较短,需要经常创建回收的对象,例如stateless
widget
。年轻代的GC比老年代的频繁很多,速度也比老年代快。配合上schedule机制,我们在APP运行的时候几乎感觉不到GC造成的卡顿。
在本质上,对象占据了内存中的连续空间。随着对象的创建,它们被分配给下一块可用的内存空间,直到所有的内存都被占满了,然后进行GC。dart
使用指针碰撞的方式来给这些对象分配空间(之所以没有空闲列表的方法是因为dart
在GC之后都会采用滑动压缩的方式来把内存碎片清除掉)。
和JVM类似,dart的年轻代也分成两个部分,在任何时候,只会有一部分被使用。
如图,在进行GC的时候,首先遍历
from
区域中的对象,判断其是否可以被回收(采用可达性分析方法),遍历完成之后将不会被回收的对象复制到to
区域中,然后from
区域中的对象全部被回收掉。最后原来的to
区域就变成from
区域。吐个槽,可能这种回收方式还会修改,改成JVM中8:1:1的方式,因为每次都只能使用一半的内存,实在是太浪费内存了。
再补个图供参考
3. 老年代
当对象经历过一定次数的GC仍然存在,或者其生命周期较长(个人猜测类似于element
和RenderObject
这种需要多次复用,可变且创建比较耗费性能),将其放入老年代区域中。
老年代采用标记整理的方法来回收对象。
- 在标记的时候,该线程中内存区域是处于不可修改的状态,类似于JVM中
stop the world
,所以这个时候可能会导致ANR
(只是类似于ANR
的表现,其产生原因还是不一样的),但是由于dart
优秀的schedule
机制和老年代GC频率很低的原因,基本上不会出现这个问题。
需要注意的是,如果APP不支持弱年代假设(即大多数对象的生命期都很短;从年老对象到年轻对象的引用非常少),上面的分代设计就不那么有效了,但是考虑到Flutter中的Widget
、Element
、RenderObject
关系,我们不需要担心这个问题。
4. isolate
与JVM内存模型不同的是,dart
中每个isolate
都有自己的独立的堆栈内存空间,其各自的GC不会影响到其他isolate
的。所以我们可以通过把部分占用内存空间较大且生命周期较短的对象方法其他isolate
中,这样即使另外一个isolate
GC了,并不会对我们显示UI的isolate
造成影响。
与isolate相关的知识请参看小德大佬的文章:
- 深入了解Flutter的isolate(1) ---- 事件循环(event loop)及代码运行顺序
- 深入了解Flutter的isolate(2) --- 创建自己的isolate
- 深入了解Flutter的isolate(3) --- Flutter的thread model(线程模型)
- 深入了解Flutter的isolate(4) --- 使用Compute写isolates
总结
dart | java | |
---|---|---|
判断对象可被回收算法 | 可达性分析 | 可达性分析 |
年轻代GC算法 | 复制 1:1 | 复制 8:1:1 |
老年代GC算法 | 标记整理 | 标记整理 |
是否发生stop the world | 是 | 是 |