Dart的内存回收机制介绍

Flutter使用dart语言作为其开发语言和运行环境。dartruntime是一直存在的,但是在debugrelease模式下有一些区别。

  • debug模式下,dart大部分组件都放在设备上,例如runtimeJIT(Android)interpreter(iOS)debugprofile services
  • release模式下,只剩下runtime,而这也是Flutter App能够运行起来的最基本组件。

《Dart的内存回收机制介绍》 debug和release模式下的dart组件

runtime中,存在一个在初始化对象时为其分配内存,对象不再被使用的时候回收内存的组件,即GC。
Flutter中存在很多对象。以Stateless Widget为例,其在State发生变化或者Widget不可见的时候不断地发生重建和销毁(注意,此处是指Widget树中的Widget,对于Element树和RenderObject树来说,elementrenderObject是可变的,而且其初始化生成需要消耗很多资源。因此在大多数情况下他们是会被回收利用的)。这些Widget的生命周期都很短,对于一个UI比较复杂的APP来说,可能会有数千个Widget需要被经常回收创建。

所以有些开发者可能会采取一些措施来避免太过频繁的GC。比如为了保持一个引用的Widget对象不会被回收,将其放在state中(这样并不是说真的不会被回收,只是创建回收的频率被降低了,因为state是属于element的,而element的生命周期是比较长的)。

这么做是没有必要的,首先Widget是一个很轻量级的对象,它的创建和回收并不会占用很多资源,真正占用资源的是ElementRenderObject。其次dart 的GC机制能够快速有效的进行对象回收,不用担心Widget创建过多导致OOM出现。
  
关于WidgetElementRenderObject的更多关系请参看这篇文章Flutter中的层级蛋糕
  

Dart GC

和Jvm类似,dart中的GC是分代的,一个是年轻代,一个是老年代。如果熟悉Jvm内存机制的童鞋可以快速略过这一部分,直接看最下面的结论。

1. schedule

首先介绍下dart中的调度机制:为了最小化GC对APP和UI的性能影响(因为dart的GC有一种类似于JVM中stop the world的机制,导致APP对事件无响应、UI无法刷新),GC通过与Flutterengine建立联系,在Flutter APP处于空闲、无用户交互、或者在后台的情况下,engine通知GC进行回收工作。这样就不会对APP和UI产生影响了。
同时GC还会使用滑动压缩(类似于JVM中标记整理中的整理)的方法来减少内存碎片的数量,从而减少内存开销。

2. 年轻代

dart内存中的年轻代和JVM中的年轻代很相似。
年轻代上存放的对象是那些生命周期较短,需要经常创建回收的对象,例如stateless widget。年轻代的GC比老年代的频繁很多,速度也比老年代快。配合上schedule机制,我们在APP运行的时候几乎感觉不到GC造成的卡顿。
在本质上,对象占据了内存中的连续空间。随着对象的创建,它们被分配给下一块可用的内存空间,直到所有的内存都被占满了,然后进行GC。dart使用指针碰撞的方式来给这些对象分配空间(之所以没有空闲列表的方法是因为dart在GC之后都会采用滑动压缩的方式来把内存碎片清除掉)。
和JVM类似,dart的年轻代也分成两个部分,在任何时候,只会有一部分被使用。

《Dart的内存回收机制介绍》 年轻代中GC

如图,在进行GC的时候,首先遍历
from区域中的对象,判断其是否可以被回收(采用可达性分析方法),遍历完成之后将不会被回收的对象复制到
to区域中,然后
from区域中的对象全部被回收掉。最后原来的
to区域就变成
from区域。

吐个槽,可能这种回收方式还会修改,改成JVM中8:1:1的方式,因为每次都只能使用一半的内存,实在是太浪费内存了。

再补个图供参考

《Dart的内存回收机制介绍》 年轻代GC

3. 老年代

当对象经历过一定次数的GC仍然存在,或者其生命周期较长(个人猜测类似于elementRenderObject这种需要多次复用,可变且创建比较耗费性能),将其放入老年代区域中。
老年代采用标记整理的方法来回收对象。

  • 在标记的时候,该线程中内存区域是处于不可修改的状态,类似于JVM中stop the world,所以这个时候可能会导致ANR(只是类似于ANR的表现,其产生原因还是不一样的),但是由于dart优秀的schedule机制和老年代GC频率很低的原因,基本上不会出现这个问题。
    《Dart的内存回收机制介绍》 老年代GC

需要注意的是,如果APP不支持弱年代假设(即大多数对象的生命期都很短;从年老对象到年轻对象的引用非常少),上面的分代设计就不那么有效了,但是考虑到Flutter中的WidgetElementRenderObject关系,我们不需要担心这个问题。

4. isolate

与JVM内存模型不同的是,dart中每个isolate都有自己的独立的堆栈内存空间,其各自的GC不会影响到其他isolate的。所以我们可以通过把部分占用内存空间较大且生命周期较短的对象方法其他isolate中,这样即使另外一个isolate GC了,并不会对我们显示UI的isolate造成影响。

《Dart的内存回收机制介绍》 dart vm与dalvik vm区别

与isolate相关的知识请参看小德大佬的文章:

总结

dartjava
判断对象可被回收算法可达性分析可达性分析
年轻代GC算法复制 1:1复制 8:1:1
老年代GC算法标记整理标记整理
是否发生stop the world

引用文章

    原文作者:Rreply
    原文地址: https://www.jianshu.com/p/eb279335afcd
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞