正式定档!QCon 北京站改期为2024年4月11-13日,地点:北京·国测国际会议会展中心 >>> 了解详情
写点什么

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:024456
用户头像

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

关注

评论

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

8 年产品经验,我总结了这些持续高效研发实践经验 · 研发篇

尔达Erda

产品 程序员 云原生 研发 效能

Auditing相关注解

Damon

7月月更

测试驱动开发(TDD)在线练功房 | 9月17日开课

ShineScrum捷行

敏捷 测试 TDD 代码 测试驱动开发

今天去 OPPO 面试,被问麻了

程序员啊叶

Java 编程 程序员 架构 java面试

手把手教你在 Vue3 中自定义指令

江南一点雨

如何选择数字孪生可视化平台

3DCAT实时渲染

数字孪生

多租户软件开发架构

力软低代码开发平台

Go语言系列:Go从哪里来,Go将去哪里?

小黑豆豆

后端 阅读 Go 语言 7月月更

更新|3DCAT实时云渲染 v2.1.2版本全新发布

3DCAT实时渲染

免费的低代码开发平台有哪些?

优秀

低代码 低代码开发平台

大型仿人机器人的技术难点和应用情况

优必选科技

机器人

喜讯!瑞云科技被授予“海上扬帆”5G融合应用专委会成员单位

3DCAT实时渲染

5G

云VR:虚拟现实专业化的下一步

3DCAT实时渲染

Cloud XR

为什么数字化未来取决于3D实时渲染

3DCAT实时渲染

实时云渲染

阿里Java架构师面试高频300题:集合+JVM+Redis+并发+算法+框架等

程序员啊叶

BATM面试Java岗:精选200+面试题及答案、6大重点规划和经验总结

程序员啊叶

Java 编程 程序员 架构 java面试

柏睿数据加入阿里云PolarDB开源数据库社区

阿里云数据库开源

开源数据库 polarDB PolarDB-X 阿里云数据库 PolarDB for PostgreSQL

博云容器云、DevOps平台斩获可信云“技术最佳实践奖”

BoCloud博云

容器 DevOps 云原生 容器云

越来越成熟的Rust,都应用了哪些场景呢?

非凸科技

rust

1000个字带你一次性搞懂JavaAgent技术,反正我是彻底服了

程序员啊叶

Java 编程 程序员 java面试 构架

算法题每日一练---第9天:第几个幸运数字

知心宝贝

算法 前端 后端 7月月更

Rainbond插件扩展:基于Mysql-Exporter监控Mysql

北京好雨科技有限公司

云原生 #Kubernetes#

从业务需求出发,开启IDC高效运维之路

鲸品堂

IDC

离谱!这本书居然将高深莫测的Java高并发知识讲解得浅显易懂

了不起的程序猿

Java Java并发 java程序员

Cloud XR面临的问题以及Cloud XR主要应用场景

3DCAT实时渲染

3DCAT v2.1.3新版本发布,这三大功能更新你不容错过!

3DCAT实时渲染

一次性把Docker的概念、容器与虚拟机的区别、容器交付的优势讲清

程序员啊叶

Java 编程 程序员 架构 java面试

优必选大型仿人服务机器人Walker X的核心技术突破

优必选科技

机器人

阿里架构师花近三个月时间整理出来的Java独家面试题(Java岗)

程序员啊叶

Java 编程 程序员 架构 java面试

城市燃气安全再拉警钟,如何防患于未“燃”?

AIRIOT

物联网 天然气管理平台 燃气安全

7.依赖注入

MASA技术团队

后端

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