blink GC 的设计
blink GC 的设计
由于最近再给自己的项目设计自动管理内存的方法,RC和GC都在考虑范围之内,但是我看到一些大项目像Chromium和UE4里都是采用的GC,所以想看一看有没有什么可以参考的地方,有关资料不算多,搬一篇过来
线程设计
- 一个线程触发GC, 则称这个线程为GCing线程 (很有可能是主线程,因为主线程分配最多的资源)
- GCing线程等待其他的线程进入安全点。安全点是指线程不改变Olipan堆中对象关系结构。通常线程都会停在安全点,但是也不一定需要停止。例如 线程不改变对象图,就可以在安全点执行同步IO,这也是到达了安全点。安全点应该在许多地方都有,因为当GCing线程要求其他的线程进入安全点时,其他的线程能够快速的进入安全点。
- 当线程进入安全点时,GCing线程进入标记阶段。GCing 线程通过每个对象上的 trace() 方法从根集合触发标记所有能标记的对象。这意味着其他线程拥有的对象同样会被标记。因为其他线程都进入了安全点,所以不会引入线程之间的竞争。
- 当标记完成以后,GCing线程将其他线程恢复到执行阶段。每个线程开始一个清除阶段,负责销毁该线程已分配的对象。这样,可以确保在分配对象的线程上破坏对象,扫描由每个线程延迟完成。线程不是一口气完成扫描阶段,而是按照分配的增量递增地扫描对象。这样有助于将较长的扫描阶段的暂停时间分配为小块。
步骤2和3是被称为GC暂停时间。暂停时间与标记阶段中标记的对象数量成正比,这意味着它与活动对象的数量成正比。
如果GCing线程在一定时间内未能停止所有其他线程,它将放弃触发GC。避免引入不可接受的长暂停时间。
GCing线程必须停止所有其他线程并不太好。例如,由于主线程上发生大量分配而导致工作线程必须参与GC。
保守GC 和 精准GC
Oilpan有两种GC。
当所有线程在事件循环结束时在安全点停止时,触发精准GC。此时,可以确保没有指向Oilpan堆的堆栈指针。因此,Oilpan运行精准GC。精准GC的根集是持久句柄。
否则,将触发保守GC。在这种情况下,GC扫描线程的本机堆栈(在事件循环结束时不会在安全点停止线程),并将通过本机堆栈发现的指针推入根集合中。
保守GC比精确GC昂贵,因为保守GC需要扫描本地堆栈。因此,Oilpan会尽最大努力在事件循环结束时触发GC。特别是,Oilpan会尽力在空闲任务中触发GC。
标记阶段
- GCing线程通过调用在每个对象上定义的trace()方法来标记从根集合中可以访问的所有对象。
- GCing线程清除所有不重要的WeakMembers
为了防止在释放后使用资源的事情发生,确保Olipan不误追踪对象图里的边很重要。这意味着除堆栈指针外的所有指针都必须用Oilpan的handle包装。指向堆上对象的原始指针存在创建Oilpan无法理解的边并造成风险。除非确定目标对象可以通过其他方式保持活动,否则不应使用指向堆上对象的原始指针(本机堆栈上的原始指针除外)。
清除阶段
清除阶段并行执行
- 线程首先清除所有的non-trivial WeakMembers. non-trivial WeakMembers 指手工弱处理,以及嵌入HeadHashMap.在标记阶段不运行non-trivial WeakMembers 的原因是因为清除它会调用析构函数。析构函数必须在同样的线程中进行析构
- 线程陷入预终结器,在这个阶段没有析构函数会被调用。因此预终结器可以访问堆上的对象
- 线程调用标记为急切完成的死对象的析构函数。有关热切完成的对象的更多详细信息,请参见以下注释。
- 该线程恢复用户代码的执行。
- 当用户代码逐渐分配新对象,惰性扫描逐渐增加其余死对象的析构函数。
无法保证析构函数的调用顺序。这就是为什么析构函数不得触碰任何其他堆上对象(这些对象可能已经被破坏)的原因。如果某些析构函数不可避免地需要触碰其他堆上对象,则需要使用预终结器。允许预终结器触碰其他堆上对象。
在所有析构函数运行之前,恢复器将恢复运行。例如,假设X是Y的客户,而Y持有客户列表的情况。如果您依赖X的析构函数从列表中删除X,则Y可能会迭代列表并调用X的某些方法,该方法可能会接触其他堆上对象。这将导致无用后使用。需要确保在用户代码以不依赖X的析构函数的方式继续执行之前,将X明确从列表中删除。
无论哪种方式,最重要的是不能保证析构函数的运行时间。不应对顺序和时间做任何假设。 (通常,在析构函数中执行复杂的操作很危险。)
注意(以下是只有在有异常的销毁要求时才需要的功能):
- 弱处理仅在WeakMember所持有的对象超过指向的对象时运行。如果持有者对象和指向对象同时死亡,则弱处理不会运行。假设弱处理总是运行,那么编写代码是错误的。
- 预终结器很繁琐,因为线程需要在每个扫描阶段扫描所有预终结器,以确定要调用哪些预终结器(线程需要调用死对象的预终结器)。应该避免将预整理器添加到频繁创建的对象中。
- 确保最终确定的对象在用户代码恢复执行之前被破坏。这意味着,允许将热切完成的对象的析构函数接触其他未热切完成的对象,而不允许其接触其他热切完成的对象。这个概念对某些对象很有用,但很讨厌。我们计划用预先修饰器替换最容易完成的对象。
- 在一个微妙的情况下,在线程完成惰性扫描之前触发了下一个GC。在这种情况下,尚未扫描的对象将标记为已死,然后下一个GC启动。标记为死的对象将在下一个GC的扫描阶段中被清除。这意味着您不能假定某些两个对象在同一GC周期中被破坏。
堆结构
每个线程都有其专用的堆,因此线程可以分配对象而无需获取锁。例如,在线程1上分配的对象与在线程2上分配的对象将进入不同的堆。
此外,每个线程都提供了多个阶段,可以按对象的类型对对象进行分组,从而提高了局部性。例如,在线程1上分配的Node对象与在线程1上分配的CSSValue对象进入不同的堆

浙公网安备 33010602011771号