生成式AI领域的最新成果都在这里!抢 QCon 展区门票 了解详情
写点什么

Google V8 的垃圾回收引擎

  • 2015-08-25
  • 本文字数:3676 字

    阅读完需:约 12 分钟

Google V8 引擎(以下简称 V8)是 Google 的一个开源项目,旨在构建一个高效的 JavaScript 引擎,是 Google 特别为 Chrome 高速运行网页应用(Web App)而开发的。同时,它可以作为一个独立的库被嵌入到其他应用程序中,以提高软件的灵活性和可扩展性。目前,V8 引擎由于其高效的性能吸引了越来越多的关注。

Google 的好几款应用都是基于 JavaScript,其中包括 Gmail 电子邮件服务、Google Maps 地图数据服务、以及 Google Docs office 套件。这些应用表现出的速度不仅受到服务器、网络、渲染引擎(Rendering Engine)等因素的影响,同时也受到 JavaScript 本身执行速度的影响。而 Google 研发的 V8 JavaScript 引擎通过采取一系列关键技术,大大提升了 JavaScript 的执行速度,关键技术包括 JIT 编译 (JIT Compile)、垃圾回收(Garbage Collection)、内嵌缓存(Inline Cache)、隐藏类等。在本文中,重点对V8 的垃圾回收引擎进行简单介绍。

什么是垃圾回收

JavaScript 的性能是关系到 Chrome 价值的一个重要方面,因为它涉及到用户能否获得一个流畅的使用体验。从 Chrome41 版本开始,通过在一些小的、零散的空闲时间内执行昂贵的内存管理操作,V8 提高了 Web 应用程序的响应能力。

许多脚本语言引擎,如 V8 引擎,对运行的应用程序实施动态的内存管理。引擎可以定期检查分配给应用程序的内存,确定哪些数据不再需要,并清除出来,以腾出内存空间。这个过程被称为垃圾回收。垃圾回收可以大幅简化程序的内存管理代码,降低程序员的负担,减少因长时间运转而带来的内存泄露问题。

什么时候执行垃圾回收

Chrome 41 版本包括了一个针对渲染引擎的任务调度器(Task Scheduler),以确保Chrome 浏览器一直保持响应和流畅,任务调度器使延迟敏感的任务拥有更高的优先级。为了实现这一目标,任务调度器需要获取多种信息,包括系统的繁忙程度,哪些任务需要被执行,以及这些任务的紧迫程度。在此基础上,任务调度器可以评估Chrome 什么时候可能空闲,以及预计会空闲多久。

举一个简单的例子,当Chrome 在网页上播放一段视频的时候。视频在屏幕上的更新速率为60 帧每秒(FPS),即Chrome 大概每次有16.6ms 的时间来进行更新。这样,Chrome 将在前一帧显示后立刻启动当前帧的工作,为当前帧执行输入和渲染任务。如果Chrome 完成所有这些工作用时不到16.6ms,在剩下的时间内, Chrome 浏览器处于闲置状态。此时,调度器通过调度一些特殊的空闲任务(Idle Tasks)可以使Chrome 能够利用这些空闲时间。如下图所示。

空闲任务是一些特殊的低优先级任务,它们在调度器确定Chrome 空闲的时候才被运行。空闲任务拥有一个截止时间,截止时间是调度器估计Chrome 能够保持空闲的时间。例如,在视频播放的例子中,截止时间是下一帧应该开始的时间。在其他情况下,截止时间可能是下一个待处理任务计划运行的时间,通常其有一个50ms 的上限,以确保Chrome 浏览器对突然的用户输入仍能保持响应。空闲任务的截止时间能够被用来估算在不会造成用户输入响应延迟的情况下Chrome 可以完成的工作量。

垃圾回收就是一种典型空闲任务,其隐藏在一些关键的、延迟敏感的任务背后。这意味着这些垃圾回收任务是在没有影响用户体验的情况下,在Chrome 的空闲时间内被执行。为了理解V8 是如何做到这一点,下面我们对V8 目前的垃圾回收策略进行深入了解。

深入了解V8 的垃圾回收引擎

V8 采用了一个分代(Generational)垃圾回收器,将内存堆分割为新生代(Young Generation)和老生代(Old Generation)。新生代的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。由于绝大多数对象的生存期很短,只有少数对象的生存期较长,这种分代策略能使垃圾回收器对新生代对象执行一些规则的、小的垃圾回收(被称为Scavenge)。V8 分别对新生代对象和老生代对象使用不同的垃圾回收算法来提升垃圾回收的效率。

对象起初会被分配在新生代内存区(通常很小,只有1-8 MB,具体根据任务分配)。大多数的对象被分配在这里,这个区域很小但是垃圾回收特别频繁。新生代使用半空间(Semi-space)分配策略,其中新对象最初分配在新生代的活跃半空间内。一旦半空间已满,一个Scavenge 操作将活跃对象移出到其他半空间中,被认为是长期驻存的对象,并被晋升为老生代。一旦活跃对象已被移出,则在旧的半空间中剩下的任何死亡对象被丢弃。

因此新生代对象的Scavenge 操作的持续时间取决于新生代中活跃对象的数量。在大部分新生代对象活跃时间不长的情况下,一个Scavenge 操作非常快(<1ms)。然而,如果大多数对象都需要被Scavenge 的时候,Scavenge 操作的持续时间显然会更长。

Scavenge 操作对于快速回收、紧缩小片内存效果很好,但对于大片内存则消耗过大。因为 Scavenge 操作需要出区和入区两个区域,这对于小片内存尚可,而对于超过数 MB 的内存就开始变得不切实际了。老生代所保存的对象大多数是生存周期很长的甚至是常驻内存的对象,而且老生代占用的内存较多,通常包含有上百 MB 的数据。因此,V8 在老生代中的垃圾回收采用标记 - 清除(Mark-Sweep)和 Mark-Compact 相结合的策略。

当老生代中的活动对象增长超过了一个预设的限制的时候,将对堆栈执行一个大回收。老生代垃圾回收使用 Mark-Sweep 策略,其采用了几种优化方法来改善延迟和内存消耗。标记时间取决于必须标记的活跃对象的数目,对于一个大的 web 应用,整个堆栈的标记可能需要超过 100ms。由于全停顿会造成了浏览器一段时间无响应,所以 V8 使用了一种增量标记的方式标记活跃对象,将完整的标记拆分成很多小的步骤,每做完一部分就停下来,让 JavaScript 的应用线程执行一会,这样垃圾回收与应用线程交替执行。V8 可以让每个标记步骤的持续时间低于 5ms。

由于标记完成后,所有对象都已经被标记,即不是活跃对象就是死亡对象,堆上有多少空间已经确定。清除时,垃圾回收器会扫描连续存放的死对象,将其变成空闲空间。这个任务是由专门的清扫线程同步执行。最后,为减少老生代对象产生的内存碎片,还要执行内存紧缩(Memory Compaction)。这个任务可能是非常耗时的,并且仅当内存碎片成为问题的时候才进行。

总之,有四个主要的垃圾回收任务:

  1. 新生代对象的 Scavenge,这通常是快速的;
  2. 通过增量方式的标记步骤,依赖于需要标记的对象数量,时间可以任意长;
  3. 完整垃圾回收,这可能需要很长的时间;
  4. 带内存紧缩的完整垃圾回收,这也可能需要很长的时间,需要进行内存紧缩。

为了在空闲时段执行这些操作,V8 给任务调度器公布垃圾回收空闲任务。当这些空闲任务运行时,它们被提供一个需要完成的截止时间。 V8 的垃圾回收空闲时间处理程序为了减少内存消耗,评估哪些垃圾回收任务应该被执行,同时紧盯截止时间以避免在帧渲染过程中出现用户输入响应延迟。

如果应用的内存分配率显示在下一个期待的空闲时间之前新生代内存区已经满了,垃圾回收器将执行新生代对象的 Scavenge 操作。此外,它还会计算最近的 Scavenge 操作所花费的平均时间,可以帮助预测未来 Scavenge 操作的持续时间,并确保它不会超出空闲任务的截止时间。

当老生代中活跃对象的数量接近堆栈限制的时候,增量标记开始。增量标记的步数与需要标记的字节数成线性比例。根据测得的平均标记速度,垃圾回收空闲时间处理程序尝试尽可能地为一个垃圾回收任务安排多的标记工作。

如果老生代内存区几乎满了,此外任务的截止时间足够长可以完成回收任务,在一个空闲任务中将调度一个完整的垃圾回收任务。回收任务的执行时间是标记速度乘以分配对象的数目。带内存紧缩的完整垃圾回收只有在 Chrome 空闲足够长的时间才被执行。

性能评价

为了评价空闲时间运行垃圾回收任务的影响,V8 使用 Chrome 的性能遥测基准框架,以评价加载热门网站时页面滚动的平滑度。选择Linux 工作站上排名前25 位的网站,以及Android Nexus 6 智能手机上的一些典型的移动网站,在两种情况下打开流行的网页(包括一些复杂的web 应用,如Gmail,Google 文档和YouTube),滚动其内容需要几秒钟。 为了保证流畅的用户体验,Chrome 的目标是滚动显示保持在60 FPS。

下图显示了空闲时间垃圾回收的比例。相比Nexus 6,工作站因为拥有更好的硬件,导致总体上拥有更多的空闲时间,从而导致其在空闲时间内拥有一个更高的垃圾回收比例(43%,而Nexus6 为31%),工作站的 jank 指标比 Nexus 6 也高了 7%。

事实上,垃圾回收是一个复杂的过程。Google V8 的垃圾回收方法能够自动完成垃圾回收,大大减轻了应用开发者的负担,能够让他们集中精力于更重要的事情上。尽管目前 V8 的垃圾回收引擎并不完美,仍存在一些性能问题而且偶尔会出现奇怪的现象,但我们还是很高兴地看到其正在变得更好,Google 的工程师 Hannes Payer 和 Ross McIlroy 在其博客中说到,他们一直在努力对垃圾回收做更多的改进。


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015-08-25 19:024476
用户头像

发布了 268 篇内容, 共 117.9 次阅读, 收获喜欢 24 次。

关注

评论

发布
暂无评论
发现更多内容

3DCAT首届行业生态交流会|瑞云科技技术总监赵志杰:实时渲染助力元宇宙应用触手可及

3DCAT实时渲染

云计算 公有云 云渲染 元宇宙

企业管理中用户数据同步机制解决思路

全象云低代码

低代码 数据同步 用户数据 后端技术

微信的业务架构&学生管理系统架构设计

随欣所遇

架构实战营

Apache APISIX 2.12.0 版本发布,新功能更适配新一年!

API7.ai 技术团队

开源 后端

微信业务架构图

Geek_36cc7c

微信架构简析和学生系统初步方案

Bear

「架构实战营」

架构设计小试牛刀

Fingal

架构实战营

做了这么多年架构师,我终于理解了什么是架构设计

博文视点Broadview

为什么 Cpython 是 C 写的,而不是 Python

宇宙之一粟

Python 1月月更

数据治理平台化的通用框架设计

Taylor

从 Gitee 到极狐GitLab 的迁移指南

极狐GitLab

迁移 gitee 极狐GitLab

TCP长连接实践与挑战

字节跳动终端技术

字节跳动 TCP 后端 HTTP 连接池

云厂商第一家,腾讯安全获国家级信息安全服务资质“双认证”!

腾讯安全云鼎实验室

云安全 安全服务

微信业务架构图 & 学生管理系统设计

tom

领域划分的规则是什么

神帅

DDD 领域划分规则

百度AICA再添57位“首席AI架构师”,人工智能与产业场景结合愈发深入

百度大脑

架构实战营模块一作业

刘璐

如何知道自己是否适合做产品经理?

石云升

产品经理 1月月更

三峡集团研究院:基于物联网的大规模储能系统能量管理和智能运维平台数据接入方案

EMQ映云科技

物联网 mqtt 碳中和 边云协同 新能源

架构实战营-模块一作业

炎彬

「架构实战营」

天津银行周传凯:从 0 到 1,我的分布式数据库落地经验谈

OceanBase 数据库

分布式 OceanBase 开源 OceanBase 社区版 客户实践

☕【Java深层系列】「并发编程系列」深入分析和研究MappedByteBuffer的实现原理和开发指南

洛神灬殇

Java 文件I/O MappedByteBuffer FileChannel 1月日更

LabVIEW实现PCB电路板元器件匹配定位(实战篇—7)

不脱发的程序猿

计算机视觉 图像处理 LabVIEW PCB电路板元器件匹配定位

模块六作业

novoer

「架构实战营」

DDD独立类模式你用到了吗

神帅

DDD 独立类模式

学生管理系统架构设计方案

Geek_36cc7c

模板

Anthony

架构图 - 微信 & 学生管理系统

Ntropy

架构实战营

架构训练营作业1

Richard

架构实战营

实现一个任务调度系统,看这篇文章就够了

勇哥java实战分享

Elastic-job XXL-JOB 任务调度 自主研发

第一次作业

Mr小公熊

Google V8的垃圾回收引擎_语言 & 开发_张天雷_InfoQ精选文章