目录
V8的内存限制与解决办法
V8垃圾回收策略
Scavenge算法
Scavenge算法中深度/广度优先的区别
新生代内存对象的晋升条件
老生代内存对象的回收算法
垃圾回收的全暂停
STW优化
减少垃圾回收的影响
首页 web前端 js教程 聊聊V8的内存管理与垃圾回收算法

聊聊V8的内存管理与垃圾回收算法

Apr 27, 2022 pm 08:44 PM
node v8引擎

本篇文章带大家了解一下V8引擎的内存管理与垃圾回收算法,希望对大家有所帮助!

聊聊V8的内存管理与垃圾回收算法

众所周知,JS是自动管理垃圾回收的,开发者不需要关心内存的分配与回收。而且垃圾回收机制在前端面试中也是常考的部分。本文主要讲解V8的分代垃圾回收算法,希望阅读本文后的小伙伴能够对V8垃圾回收机制有个痛彻(哈哈,是痛彻!!!)的了解,文章主要涵盖如下内容:

  • V8的内存限制与解决办法
  • 新生代内存对象的Scavenge算法
  • 基于可达性分析算法标记存活对象的逻辑以及优化手段
  • 新生代内存对象的晋升条件、
  • Scavenge算法的深度/广度优先区别
  • 跨代内存的的写屏障
  • 老生代内存对象的标记清除/整理算法
  • GCSTW原因及优化策略

V8的内存限制与解决办法

V8最初为浏览器设计,遇到大内存使用的场景较少,在设计上默认对内存使用存在限制,只允许使用部分内存,64位系统可允许使用内存约1.4g,32位系统约0.7g。如下代码所示,在Node中查看所依赖的V8引擎的内存限制方法:

process.memoryUsage();

// 返回内存的使用量,单位字节
{
  rss: 22953984,
  // 申请的总的堆内存
  heapTotal: 9682944,
  // 已使用的堆内存
  heapUsed: 5290344,
  external: 9388
}
登录后复制

聊聊V8的内存管理与垃圾回收算法

V8限制内存使用大小还有另一个重要原因,堆内存过大时V8执行垃圾回收的时间较久(1.5g50ms),做非增量式的垃圾回收要更久(1.5g1s)。在后续讲解了V8的垃圾回收机制后相信大家更能感同身受。

虽然V8引擎对内存使用做了限制,但是同样暴露修改内存限制的方法,就是启动V8引擎时添加相关参数,下面代码演示在Node中修改依赖的V8引擎内存限制:

# 更改老生代的内存限制,单位mb
node --max-old-space-size=2048 index.js

# 更改新生代的内存限制,单位mb
node --max-semi-space-size=1024=64 index.js
登录后复制

这里需要注意的是更改的新生代的内存的语法已经更改为上述的写法,且单位也由kb变成了mb,旧的写法是node --max-new-space-size,可以通过下面命令查询当前Node环境修改新生代内存的语法:

node --v8-options | grep max
登录后复制

聊聊V8的内存管理与垃圾回收算法

V8垃圾回收策略

在引擎的垃圾自动回收机制的历史演变中,人们发现是没有一种通用的可以解决任何场景下垃圾回收的算法的。因此现代垃圾回收算法根据对象的存活时间将内存垃圾进行分代分代垃圾回收算法就是对不同类别的内存垃圾实行不同的回收算法。

V8将内存分为新生代老生代两种:

  • 新生代内存中的对象存活时间较短
  • 老生代内存中代对象存活时间较长或是常驻内存

新生代内存存放在新生代内存空间(semispace)中,老生代内存存放在老生代内存空间中(oldspace),如下图所示:

聊聊V8的内存管理与垃圾回收算法

  • 新生代内存采用Scavenge算法
  • 老生代内存采用Mark-SweepMark-Compact算法

下面我们看看Scavenge的算法逻辑吧!

Scavenge算法

对于新生代内存的内存回收采用Scavenge算法,Scavenge的具体实现采用的是Cheney算法。Cheney算法是将新生代内存空间一分为二,一个空间处于使用状态(FromSpace),一个空间处于空闲状态(称为ToSpace)。

聊聊V8的内存管理与垃圾回收算法

在内存开始分配时,首先在FromSpace中进行分配,垃圾回收机制执行时会检查FromSpace中的存活对象,存活对象会被会被复制到ToSpace,非存活对象所占用的空间将被释放,复制完成后FromSpaceToSpace的角色将翻转。当一个对象多次复制后依然处于存活状态,则认为其是长期存活对象,此时将发生晋升,然后该对象被移动到老生代空间oldSpace中,采用新的算法进行管理。

聊聊V8的内存管理与垃圾回收算法

Scavenge算法其实就是在两个空间内来回复制存活对象,是典型的空间换时间做法,所以非常适合新生代内存,因为仅复制存活的对象且新生代内存中存活对象是占少数的。但是有如下几个重要问题需要考虑:

  • 引用避免重复拷贝

假设存在三个对象temp1、temp2、temp3,其中temp2、temp3都引用了temp1,js代码示例如下:

var temp2 = {
  ref: temp1,
}

var temp3 = {
  ref: temp1,
}

var temp1 = {}
登录后复制

FromSpace中拷贝temp2ToSpace中时,发现引用了temp1,便把temp1也拷贝到ToSpace,是一个递归的过程。但是在拷贝temp3时发现也引用了temp1,此时再把temp1拷贝过去则重复了。

要避免重复拷贝,做法是拷贝时给对象添加一个标记visited表示该节点已被访问过,后续通过visited属性判断是否拷贝对象。

  • 拷贝后保持正确的引用关系

还是上述引用关系,由于temp1不需要重复拷贝,temp3被拷贝到ToSpace之后不知道temp1对象在ToSpace中的内存地址。

做法是temp1被拷贝过去后该对象节点上会生成新的field属性指向新的内存空间地址,同时更新到旧内存对象的forwarding属性上,因此temp3就可以通过旧temp1forwarding属性找到在ToSpace中的引用地址了。

内存对象同时存在于新生代和老生代之后,也带来了问题:

  • 内存对象跨代(跨空间)后如何标记
const temp1 = {}

const temp2 = {
  ref: temp1,
}
登录后复制

比如上述代码中的两个对象temp1temp2都存在于新生代,其中temp2引用了temp1。假设在经过GC之后temp2晋升到了老生代,那么在下次GC的标记阶段,如何判断temp1是否是存活对象呢?

在基于可达性分析算法中要知道temp1是否存活,就必须要知道是否有根对象引用引用了temp1对象。如此的话,年轻代的GC就要遍历所有的老生代对象判断是否有根引用对象引用了temp1对象,如此的话分代算法就没有意义了。

解决版本就是维护一个记录所有的跨代引用的记录集,它是写缓冲区的一个列表。只要有老生代中的内存对象指向了新生代内存对象时,就将老生代中该对象的内存引用记录到记录集中。由于这种情况一般发生在对象写的操作,顾称此为写屏障,还一种可能的情况就是发生在晋升时。记录集的维护只要关心对象的写操作和晋升操作即可。此是又带来了另一个问题:

  • 每次写操作时维护记录集的额外开销

优化的手段是在一些Crankshaft操作中是不需要写屏障的,还有就是栈上内存对象的写操作是不需要写屏障的。还有一些,更多的手段就不在这里过多讨论。

  • 缓解Scavenge算法内存利用率不高问题

新生代内存中存活对象占比是相对较小的,因此可以在分配空间时,ToSpace可以分配的小一些。做法是将ToSpace空间分成S0S1两部分,S0用作于ToSpaceS1与原FromSpace合并当成FromSpace

聊聊V8的内存管理与垃圾回收算法

Scavenge算法中深度/广度优先的区别

垃圾回收算法中,识别内存对象是否是垃圾的机制一般有两种:引用计数基于可达性分析

基于可达性分析,就是找出所有的根引用(比如全局变量等),遍历所有根引用,递归根引用上的所有引用,凡是被遍历到的都是存活对象并打上标记,此时空间中的其他内存对象都是死对象,由此构建了一个有向图

考虑到递归的限制问题,递归逻辑一般采用非递归实现,常见的有广度优先和深度优先算法。两者的区别在于:

  • 深度优先拷贝到ToSpace时改变了内存对象的排列顺序,使得有引用关系的对象距离较近。原因是拷贝完自己之后直接拷贝自己引用的对象,因此相关的对象便在ToSpace中靠的较近
  • 深度优先正好相反

因为CPU的缓存策略,会在读取内存对象时有很大概率把他后面的对象一起读,目的是为了更快的命中缓存。因为在代码开发期间很常见的场景就是obj1.obj2.obj3,此时CPU读取obj1时如果把后面的obj2obj3一起读的话,则很利于命中缓存。

所以深度优先的算法更利于业务逻辑命中缓存,但是其实现需要依赖额外的栈辅助实现算法,对内存空间有消耗。广度优先则相反,无法提升缓存命中,但是其实现可以利用指针巧妙的避开空间消耗,算法的执行效率高。

新生代内存对象的晋升条件

新生代中的内存对象如果想晋升到老生代需要满足如下几个条件:

  • 对象是否经历过Scavenge回收
  • ToSpace的内存使用占比不能超过限制

判断是否经历过Scavenge的GC的逻辑是,每次GC时给存活对象的age属性 1,当再次GC的时候判断age属性即可。基本的晋升示意图如下所示:

聊聊V8的内存管理与垃圾回收算法

老生代内存中,长期存活的对象较多,无法采取Scavenge算法回收的原因在于:

  • 存活对象较多导致复制效率低下
  • 浪费了一半的内存空间

老生代内存对象的回收算法

老生代内存空间的垃圾回收采用的是标记清除Mark-Sweep)和标记整理Mark-Compact)结合的方式。标记清除分为两部分:

  • 标记阶段
  • 清除阶段(如果是标记整理则是整理阶段)

在标记阶段遍历老生代堆内存中的所有内存对象,并对活着的对象做标记,清除阶段只清理未被标记的对象。原因是:老生代内存中非存活对象占少数。

聊聊V8的内存管理与垃圾回收算法

如上图所示,标记清除存在的一个问题是清理之后存在了不连续的空间导致无法继续利用,所以对于老生代内存空间的内存清理需要结合标记整理的方案。该方案是在标记过程中将活着的对象往一侧移动,移动完成后再清理界外的所有非存活对象移除。

聊聊V8的内存管理与垃圾回收算法

垃圾回收的全暂停

垃圾回收时需要暂停应用执行逻辑,待垃圾回收机制结束后再恢复应用执行逻辑,该行为称为“全暂停”,也就是常说的Stop The World,简称STW。对新生代内存的垃圾回收该行为对应用执行影响不大,但是老生代内存由于存活对象较多,所以老生代内存的垃圾回收造成的全停顿影响非常大。

聊聊V8的内存管理与垃圾回收算法

V8为了优化GC的全暂停时间,还引入了增量标记并发标记并行标记增量整理并行清理延迟清理等方式。

STW优化

衡量垃圾回收所用时间的一个重要指标是执行 GC 时主线程暂停的时间量。STW所带来的影响是无法接受的,因此V8也采取的很多优化手段。

  • 并行GC

GC的过程需要做大量的事情从而在主线程上导致STW现象,并行GC的做法是开多个辅助线程分担GC的事情。该做法依然无法避免STW现象的,但是可以减少STW的总时间,取决于开启的辅助线程数量。

1聊聊V8的内存管理与垃圾回收算法

  • 增量GC

增量GC将GC工作进行拆分,并在主线程中间歇的分步执行。该做法并不会减少GC的时间,相反会稍微花销,但是它同样会减少GC的STW的总时间。

1聊聊V8的内存管理与垃圾回收算法

  • 并发GC

并发GC是指GC在后台运行,不再在主线程运行。该做法会避免STW现象。

1聊聊V8的内存管理与垃圾回收算法

  • 空闲时间GC

Chrome中动画的渲染大约是60帧(每帧约16ms),如果当前渲染所花费时间每达到16.6ms,此时则有空闲时间做其他事情,比如部分GC任务。

1聊聊V8的内存管理与垃圾回收算法

减少垃圾回收的影响

想要提高执行效率要尽量减少垃圾回收的执行和消耗:

  • 慎把内存当作缓存,小心把对象当作缓存,要合理限制过期时间和无限增长的问题,可以采用lru策略

  • Node中避免使用内存存储用户会话,否则在内存中存放大量用户会话对象导致老生代内存激增,影响清理性能进而影响应用执行性能和内存溢出。改进方式使用使用redis等。将缓存转移到外部的好处:

    • 减少常驻内存对象的数量,垃圾回收更高效
    • 进程之间可以共享缓存

更多node相关知识,请访问:nodejs 教程

以上是聊聊V8的内存管理与垃圾回收算法的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

nvm 怎么删除node nvm 怎么删除node Dec 29, 2022 am 10:07 AM

nvm删除node的方法:1、下载“nvm-setup.zip”并将其安装在C盘;2、配置环境变量,并通过“nvm -v”命令查看版本号;3、使用“nvm install”命令安装node;4、通过“nvm uninstall”命令删除已安装的node即可。

node项目中如何使用express来处理文件的上传 node项目中如何使用express来处理文件的上传 Mar 28, 2023 pm 07:28 PM

怎么处理文件上传?下面本篇文章给大家介绍一下node项目中如何使用express来处理文件的上传,希望对大家有所帮助!

Node服务怎么进行Docker镜像化?极致优化详解 Node服务怎么进行Docker镜像化?极致优化详解 Oct 19, 2022 pm 07:38 PM

这段时间在开发一个腾讯文档全品类通用的 HTML 动态服务,为了方便各品类接入的生成与部署,也顺应上云的趋势,考虑使用 Docker 的方式来固定服务内容,统一进行制品版本的管理。本篇文章就将我在服务 Docker 化的过程中积累起来的优化经验分享出来,供大家参考。

深入浅析Node的进程管理工具“pm2” 深入浅析Node的进程管理工具“pm2” Apr 03, 2023 pm 06:02 PM

本篇文章给大家分享Node的进程管理工具“pm2”,聊聊为什么需要pm2、安装和使用pm2的方法,希望对大家有所帮助!

Pi Node教学:什么是Pi节点?如何安装和设定Pi Node? Pi Node教学:什么是Pi节点?如何安装和设定Pi Node? Mar 05, 2025 pm 05:57 PM

PiNetwork节点详解及安装指南本文将详细介绍PiNetwork生态系统中的关键角色——Pi节点,并提供安装和配置的完整步骤。Pi节点在PiNetwork区块链测试网推出后,成为众多先锋积极参与测试的重要环节,为即将到来的主网发布做准备。如果您还不了解PiNetwork,请参考Pi币是什么?上市价格多少?Pi用途、挖矿及安全性分析。什么是PiNetwork?PiNetwork项目始于2019年,拥有其专属加密货币Pi币。该项目旨在创建一个人人可参与

聊聊用pkg将Node.js项目打包为可执行文件的方法 聊聊用pkg将Node.js项目打包为可执行文件的方法 Dec 02, 2022 pm 09:06 PM

​如何用pkg打包nodejs可执行文件?下面本篇文章给大家介绍一下使用pkg将Node项目打包为可执行文件的方法,希望对大家有所帮助!

npm node gyp失败怎么办 npm node gyp失败怎么办 Dec 29, 2022 pm 02:42 PM

npm node gyp失败是因为“node-gyp.js”跟“Node.js”版本不匹配,其解决办法:1、通过“npm cache clean -f”清除node缓存;2、通过“npm install -g n”安装n模块;3、通过“n v12.21.0”命令安装“node v12.21.0”版本即可。

什么是单点登录系统?用nodejs怎么实现? 什么是单点登录系统?用nodejs怎么实现? Feb 24, 2023 pm 07:33 PM

什么是单点登录系统?用nodejs怎么实现?下面本篇文章给大家介绍一下使用node实现单点登录系统的方法,希望对大家有所帮助!

See all articles