[译] Getting garbage collection for free

185 阅读7分钟

Getting garbage collection for free

原文链接:v8.dev/blog/free-g…

翻译本文的目的是尝试开拓视野,汲取新的知识,如有翻译不正确的地方,欢迎指正。

JavaScript的性能依旧是Chrome的价值关键因素之一,尤其是当它涉及到提供流畅的体验时。自从Chrome 41开始,V8利用新的技术通过隐藏昂贵内存管理操作在小的,空闲的时间里来提高web浏览器的响应的速度。因此,web开发者期望由于垃圾收集而使滚动和buttery动画(可以理解为流畅的动画?)更为的流畅并且大量的减少jank错误。


Jank

Jank既指“卡顿”表现,通常是由于在主线程上执行长任务,阻止渲染或在后台进程上消耗过多处理器能力所致。


许多的现代语言引擎像chrome V8的JavaScript引擎动态的管理内存在运行中的应用程序上,因此开发人员就不需要担心了。这个引擎定期的分配内存给应用程序,确定哪些数据将不再需要,并且清除它们并释放空间。这个过程称之为垃圾收集

在Chrome中,我们努力去实现流畅(平稳), 60帧每秒(FPS)的视觉体验。尽管V8已经尝试在小的片段中进行垃圾收集,但较大的垃圾收集操作可能在不确定的时间执行,有时会在动画中间,暂停执行,使得chrome达不到60FPS。

chrome41包含了一个用于Blink呈现引擎的任务计划程序,该任务计划可以对延迟敏感的任务(DOM操作等)进行优先级排序,以确保chrome保持响应能力和快速响应性。除了能考虑工作的优先级,在这个计划程序中还有系统的繁忙程度、需要执行那些任务和任务的紧急程度。因此,他可以估算出chrome何时处于空闲状态,以及它预计会闲置多长时间。

当Chrome在网页中显示动画时,就会发生这种情况。 动画将以60 FPS的速度更新屏幕,使Chrome大约有16.6毫秒的时间来执行更新。因此,Chrome将在显示前一帧后立即在当前帧上开始工作,并为该新帧执行输入,动画和帧渲染任务。 如果Chrome在不到16.6毫秒内完成了所有这些工作,则在需要开始渲染下一帧之前的剩余时间内,它无需执行其他任何操作。 Chrome的调度器可让V8在Chrome闲置时安排特殊的闲置任务,从而利用此闲置时间。

空闲任务是特殊的低优先级任务,它们在调度程序确定其处于空闲时间段时运行。 空闲任务有一个截止时间,这是调度程序对它预计保持空闲状态的估计。 在图1的动画示例中,这将是开始绘制下一帧的时间。 在其他情况下(例如,当没有任何屏幕活动发生时),这可能是安排运行下一个待处理任务的时间,上限为50毫秒,以确保Chrome保持对意外用户输入的响应。 空闲任务使用截止时间来估计它可以完成多少工作,而不会引起混乱或输入响应延迟。

Deep dive into V8’s garbage collection engine 深入了解V8的垃圾收集引擎

V8使用了分代垃圾收集器,将Javascript堆分为较小的新生代和较大老生代,分别用于新近对象和 long living对象,大多数对象在较为年轻的时候就死去了,这个分代策略允许垃圾收集器定期执行在较小的新生代中进行短暂的垃圾收集(称为 清除),而不必跟踪老代中的对象。

新生代采用了半空间的分配策略,新的对象初始分配在新生代的active空间。一旦active空间将要充满,清除操作会将live对象移动到另一半空间。曾经被移动过一次的将被提升至老生代并被认为是long-living对象。一旦active对象被移动完毕,新的半空间则是active,旧的半空间中剩余的对象将被丢弃。

因此,新生代的清除时间取决于新生代中活动对象的大小。清除是很快的(<1 ms) 当大多数对象变的 unreachable。 如果大多数对象在清除后依旧存活,那么清除的持续时间可能会延长。

当老生代中的live对象超出限制时,将执行整个堆的主要收集。老一代使用带有多种优化功能的标记清除收集器来改善延迟和内存消耗。标记等待时间取决于必须标记的live对象数量,对于大型的web应用,标记堆的时间可能超过100ms。为了避免暂停主线程这么长时间,V8一直以来是采取了在许多小步骤中标记live 对象,目的是使得每个标记步骤持续保持5ms以下。

标记之后,通过清理整个老生代内存,可以为应用程序再次提供可用的空闲内存。这个任务由专门的清理线程执行。最终,执行内存压缩来减少老生代中的内存碎片。这个任务可能非常耗时,仅仅在内存碎片成为问题时才会执行。

总而言之,有四个主要的垃圾收集任务:

  1. Young generation scavenges, which usually are fast(新生代的清除工作通常很快)
  2. Marking steps performed by the incremental marker, which can be arbitrarily long depending on the step size(由增量标记器执行的标记步骤,可以根据步长而任意延长)
  3. Full garbage collections, which may take a long time(完整的垃圾收集,这可能需要很长时间)
  4. Full garbage collections with aggressive memory compaction, which may take a long time, but clean up fragmented memory.(完整的垃圾回收和(aggressive)及时?的内存压缩,这可能需要很长时间,但是会清理零碎的内存)

为了在空闲时间执行这些操作, V8将垃圾收集空闲任务发送给调度器。当这些空闲任务执行时,它们会提供什么时候完成的最后期限。V8的垃圾收集空闲时间程序来评估执行那些垃圾收集任务能减少内存消耗,同时考虑最后截止日期,以避免将来在帧渲染或者输入延迟方面出现jank。

如果应用程序的测量分配率显示在下一个预期的空闲时间段之前,年轻一代可能已满,则垃圾收集器将在空闲任务期间执行年轻一代的清理工作。此外,它会计算最近清理任务所花费的平均时间,以预测未来清理工作的持续时间,并确保它不会违反空闲任务的截止时间。

当老生代中的live 对象大小接近堆的限制时,开始执行增量标记。增量标记步骤可以根据应标记的字节数线性缩放。 根据平均测得的标记速度,垃圾收集空闲时间处理程序将尝试使尽可能多的标记工作适合给定的空闲任务。

如果旧代快满了,并且估计提供给任务的截止日期足够长以完成收集,则在空闲任务期间安排完整的垃圾收集。 根据标记速度乘以分配对象的数量来预测收集暂停时间。 仅当网页空闲了相当长的时间后,才执行带有额外压缩的完全垃圾收集。