Node内存管理+垃圾回收机制

news2024/11/15 12:45:32

最近看到《深入浅出node》这本书,里面正好有内存控制, 加上最近看到一篇文章,也是讲了内存管理和垃圾回收机制。

由于自己曾经做过一个ssl接口,导致node服务经常重启,我潜意识就怀疑是内存管理出现了问题,所以这次来专门学习这块知识点,下面是自己的一些分享。

简短知识点如下:

  • 新生代空间: 用于存活较短的对象
  • 老生代空间: 用于存活时间较长的对象
  • 存活的对象从 from space 转移到 to space
  • 清空 from space
  • from space 与 to space 互换
  • 完成一次新生代 GC
  • 又分成两个空间: from 空间 与 to 空间
  • Scavenge GC 算法: 当 from 空间被占满时,启动 GC 算法
  • 增量标记:小模块标记,在代码执行间隙执,GC 会影响性能
  • 并发标记:不阻塞 js 执行
  • 经历过一次以上 Scavenge GC 的对象
  • 当 to space 体积超过 25%
  • 从 新生代空间 转移到 老生代空间 的条件(这个过程称为对象晋升)
  • 标记清除算法:标记存活的对象,未被标记的则被释放

堆和栈的区别

首先,需要搞清楚,哪些数据如何存储的?

基本数据类型,也就是我们常说的,number,Boolean,null,undefined,string,这些数据类型是存储在栈中。(注意:null类型是对象类型,但是存储在栈中)

而通常object存储在堆中。

堆和栈的区别如下表:

栈(先进后出)堆(无规律)
速度
空间管理高效,不会产生碎片会产生内存碎片
访问权限只能局部变量可以访问全局变量
空间大小限制操作系统限制没有特定的限制
内存分配连续随机分配
分配和释放编译器指令自动管理程序员手动管理
开销
主要问题空间小内存碎片
灵活性固定大小可以resize

那我们常听到的内存泄露?是怎么产生的?是堆内存?还是栈内存呢?

栈的空间由操作系统负责管理,开发者无需过于关心;堆的空间由 V8 引擎进行管理,可能由于代码问题出现内存泄漏,或者长时间运行后,垃圾回收导致程序运行速度变慢。

没有使用 var/let/const 声明的变量会直接绑定在 Global 对象上(Node.js 中)或者 Windows 对象上(浏览器中),哪怕不再使用,仍不会被自动回收。

接下来主要讲解Node堆内存

V8的内存限制

在后端开发语言中,在基本的内存上几乎没有限制,然而在Node中通过JavaScript使用内存就会发现只能使用部分内存,在这种限制下,将会导致Node无法直接操作大内存对象。

Node基于v8构建,所以Node中使用JavaScript对象基本上是都是通过V8引擎来分配和管理。

当我们在代码中声明变量并赋值时,所使用的对象的内存就分配在堆中。如果已申请的堆空间内存不够分配新的对象,将继续申请堆内存,直到堆的大小超过V8限制为止。

为什么V8要限制堆的大小?V8的限制最初只是为浏览器而设计的,不太可能遇到大量内存的场景。对于网页来说,V8的限制值已经绰绰有余。深层原因是V8的垃圾回收机制的限制。

V8中内存使用的查询方式

node
process.memoryUsage()

以1.5GB的垃圾回收堆内存为例,V8做一次小的垃圾回收需要50毫秒以上,做一次非增量式的垃圾回收机制甚至要1秒以上。这是垃圾回收中引起JavaScript线程短暂执行的时间,在这样的花销下,应用的性能和响应能力都会直线下降。这样的情况不仅仅后端服务无法接收,前端浏览器也无法接受。因此在当时的考虑下,直接限制堆内存是一个最好的选择。

当然这个限制也不是不能打开,V8仍然提供了选项让我们使用更多的内存。Node启动时可以传递–max-old-space-size或者–max-new-space-size来调整内存限制大小。实例如下:

node --max-old-space-size=1700 test.js // 单位为MB
node --max-new-space-size=1024 test.js // 单位为MB

上述参数在V8初始化时生效,一旦生效就不能在动态改变。如果遇到Node无法分配足够内存给JavaScript对象的情况,可以用这个办法来放宽V8默认的内存限制,避免在执行过程中稍微多用一些内存就轻易崩溃。

接下来,我们深入了解一下V8的垃圾回收策略。

V8的垃圾回收机制

V8的垃圾回收策略主要基于分布式垃圾回收机制。在自动垃圾回收的演变中,人们发现没有一种垃圾回收算法能够胜任所有场景。因为在实际的应用中,对象的生命周期长短不一,不同的算法只能针对特定情况具有最好的效果。为此统计学在垃圾回收算法的发展中产生了较大的作用,现代的垃圾回收算法中按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同分代实施更加高效的算法。

v8的内存分代

将内存分为新生代和老生代两代。

新生代和老生代空间

V8的分代内存示意图如下:

V8堆的整体大小是新生代的所用空间加上老生代的内存空间。

前面提到的–max-old-space-size命令行参数就是设置老生代的内存空间的最大值,–max-new-space-size就是设置新生代的内存空间最大值。比较特别的是两个参数必须要在启动时就指定。这就意味着V8使用的内存没有办法根据使用情况自动扩充。当内存分配过程中超过极限值时,就会引起进程出错。

在默认设置中,如果一直分配内存,在64位的系统和32位的系统下分别只能使用28GB和1.4GB的大小。这个限制可以从V8源码中找到。

在 64 位系统下,Node.js 可以使用最大约 28GB 的内存(实际上还依赖于机器内存)。这是因为 V8 的 64 位版本使用 full 128-bit pointer.

在 32 位系统下,Node.js 可以使用最大约 1.4GB 的内存。这是因为 V8 的 32 位版本使用 smi pointer ,其中 30 位用于存储指针,能够寻址的内存空间为 2 的 30 次方,约 1.4GB。

总结:

  • 新生代内存(New Space):

用于存储新创建的对象,大小默认为 512MB 到 700MB 左右(以 128MB 为增量调整)。当新生代内存不足以存储新创建的对象时,会触发 Minor GC 进行内存回收。

  • 老生代内存(Old Space):

用于存储经 Minor GC 后存活的对象,大小根据机器内存不同,可以达到 28GB(64位)或 1.4GB(32位)。当老生代内存不足时,会触发 Major GC 进行内存回收。

Scavenge回收

在分代的基础上,新生代的对象主要是通过Scavenge算法进行回收的。在Scavenge的具体实现中,主要采用Cheney算法,该算法由C.J.Cheney于1970年首次发表在ACM论文上。

Cheney算法是一种采用复制的方式实现的垃圾回收算法。它将堆内存一分为二,每一部分空间称为semispace。

在这两个semispace空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的semispace空间称为From空间,处于闲置状态的空间称为To空间。当我们分配对象时,先是在From空间中进行分配。

开始进行垃圾回收时,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。

简而言之,在垃圾回收的过程中,就是通过将存活对象在两个semispace空间之间进行复制。

Scavenge的缺点是只能使用堆内存中的一半,这是由划分空间和复制机制所决定的。但Scavenge由于只复制存活的对象,并且对于生命周期短的场景存活对象只占少部分,所以它在时间效率上有优异的表现。

由于Scavenge是典型的牺牲空间换取时间的算法,所以无法大规模地应用到所有的垃圾回收中。但可以发现,Scavenge非常适合应用在新生代中,因为新生代中对象的生命周期较短,恰恰适合这个算法。
是故,V8的堆内存示意图应当如下图所示。

实际使用的堆内存是新生代中的两个semispace空间大小和老生代所用内存大小之和。

当一个对象经过多次复制依然存适时,它将会被认为是生命周期较长的对象。这种较长生命周期的对象随后会被移动到老生代中,采用新的算法进行管理。对象从新生代中移动到老生代中的过程称为晋升。

在单纯的Scavenge过程中,From空间中的存活对象会被复制到To空间中去,然后对From空间和To空间进行角色对换(又称翻转)。但在分代式垃圾回收的前提下,From空间中的存活对象在复制到To空间之前需要进行检查。在一定条件下,需要将存活周期长的对象移动到老生代中,也就是完成对象晋升

对象晋升的条件主要有两个,一个是对象是否经历过Scavenge回收,一个是To空间的内存占用比超过限制。

在默认情况下,V8的对象分配主要集中在From空间中。对象从From空间中复制到To空间时,会检查它的内存地址来判断这个对象是否已经经历过一次Scavenge回收。如果已经经历过了,会将该对象从From空间复制到老生代空间中,如果没有,则复制到To空间中。这个晋升流程如下图所示。

另一个判断条件是To空间的内存占用比。当要从From空间复制一个对象到To空间时,如果
To空间已经使用了超过25%,则这个对象直接晋升到老生代空间中,这个晋升的判断示意图如下图

设置25%这个限制值的原因是当这次Scavenge回收完成后,这个To空间将变成From空间,接下来的内存分配将在这个空间中进行。如果占比过高,会影响后续的内存分配。

对象晋升后,将会在老生代空间中作为存活周期较长的对象来对待,接受新的回收算法处理。

对于老生代中的对象,由于存活对象占较大比重,再采用Scavenge的方式会有两个问题:一个是存活对象较多,复制存活对象的效率将会很低;另一个问题依然是浪费一半空间的问题。这两个问题导致应对生命周期较长的对象时Scavenge会显得捉襟见肘。为此,V8在老生代中主要采用了Mark-Sweep和Mark-Compact相结合的方式进行垃圾回收。

Mark-Sweep回收

Mark-Sweep是标记清除的意思,它分为标记和清除两个阶段。

与Scavenge相比,Mark-Sweep并不将内存空间划分为两半,所以不存在浪费一半空间的行为。

与Scavenge复制活着的对象不同,Mark-Sweep在标记阶段遍历堆中的所有对象,并标记活着的对象,在随后的清除阶段中,只清除没有被标记的对象。可以看出,Scavenge中只复制活着的对象,而Mark-Sweep只清理死亡对象。活对象在新生代中只占较小部分,死对象在老生代中只占较小部分,这是两种回收方式能高效处理的原因。

下图为Mark-Sweep在老生代空间中标记后的示意图,红色部分标记为死亡的对象。

Mark-Sweep最大的问题是在进行一次标记清除回收后,内存空间会出现不连续的状态。这种内存碎片会对后续的内存分配造成问题,因为很可能出现需要分配一个大对象的情况,这时所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这次回收是不必要的。

Mark-Compact回收

为了解决Mark-Sweep的内存碎片问题,Mark-Compact被提出来。Mark-Compact是标记整理的意思,是在Mark-Sweep的基础上演变而来的。它们的差别在于对象在标记为死亡后,在整理的过程中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存。下图为Mark-Compact完成标记并移动存活对象后的示意图,白色格子为存活对象,深色格子为死亡对象,浅色格子为存活对象移动后留下的空洞。

完成移动后,就可以直接清除最右边的存活对象后面的内存区域完成回收。
这里将Mark-Sweep和Mark-Compact结合着介绍不仅仅是因为两种策略是递进关系,在V8的
回收策略中两者是结合使用的。下表是目前介绍到的3种主要垃圾回收算法的简单对比。

回收算法ScavengeMark-SweepMark-Compact
速度最快中等最慢
空间开销双倍空间(无碎片)少(有碎片)少(无碎片)
是否移动对象

从表中可以看到,在Mark-Sweep和Mark-Compact之间,由于Mark-Compact需要移动对象,所以它的执行速度不可能很快,所以在取舍上,V8主要使用Mark-Sweep,在空间不足以对从新生代中晋升过来的对象进行分配时才使用Mark-Compact。

Incremental Marking

为了避免出现JavaScript应用逻辑与垃圾回收器看到的不一致的情况,垃圾回收的3种基本算法都需要将应用逻辑暂停下来,待执行完垃圾回收后再恢复执行应用逻辑,这种行为被称为“全停顿”(stop-the-world)。

在V8的分代式垃圾回收中,一次小垃圾回收只收集新生代,由于新生代默认配置得较小,且其中存活对象通常较少,所以即便它是全停顿的影响也不大。但V8的老生代通常配置得较大,且存活对象较多,全堆垃圾回收(full垃圾回收)的标记、清理、整理等动作造成的停顿就会比较可怕,需要设法改善。

为了降低全堆垃圾回收带来的停顿时间,V8先从标记阶段入手,将原本要一口气停顿完成的动作改为增量标记(incremental marking),也就是拆分为许多小“步进”,每做完一“步进”就让JavaScript应用逻辑执行一小会儿,垃圾回收与应用逻辑交替执行直到标记阶段完成。下图为增量标记示意图。

V8在经过增量标记的改进后,垃圾回收的最大停顿时间可以减少到原本的1/6左右。
V8后续还引人了延迟清理(lazy sweeping)与增量式整理(incremental compaction)、让清理与整理动作也变成增量式的。同时还计划引入并行标记与并行清理,进一步利用多核性能降低每次停顿的时间。

总结:

从V8的自动垃圾回收机制的设计角度可以看到,V8对内存使用进行限制的缘由。

新生代设计为一个较小的内存空间是合理的,而老生代空间过大对于垃圾回收并无特别意义。

V8对内存限制的设置对于Chrome浏览器这种每个选项卡页面使用一个V8实例而言,内存的便用是绰绰有余了。

对于Node编写的服务器端来说,内存限制也并不影响正常场景下的使用。但是对于V8的垃圾回收特点JavaScript在单线程上的执行情况,垃圾回收是影响性能的因素之一。想要高性能的执行效率,需要注意让垃圾回收尽量少地进行,尤其是全堆垃圾回收。

以Web服务器中的会话实现为例,一般通过内存来存储,但在访问量大的时候会导致老生代中的存活对象骤增,不仅造成清理/整理过程费时,还会造成内存紧张,甚至溢出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/657408.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

H264 NALU分析

标题 1.H264介绍2.H264编解码解析2.1. H264编码原理2.2 H264的I帧,P帧,B帧2.3 H264编码结构解析2.4 NALU2.4.1 NALU结构2.4.2 解析NALU2.4.3 annexb模式 1.H264介绍 H.264从1999年开始,到2003年形成草案,最后在2007年定稿有待核实。在ITU的标准⾥称为H.…

Ansible Playbook

Ansible 的脚本 --- playbook 剧本 playbooks 本身由以下各部分组成 (1)Tasks:任务,即通过 task 调用 ansible 的模板将多个操作组织在一个 playbook 中运行 (2)Variables:变量 (3&…

Postman接口测试之:添加Cookie伪造请求

1,获取cookie值 登录某网站,通过开发者工具(或者fiddler抓包工具),获取登录成功后的请求头中的cookie值。 大家肯定奇怪,明明访问首页的时候就已经生成了cookie值,为什么还登录呢?…

Linux中磁盘管理之格式化、分区、挂载-详解

今天给大家介绍一下Linux中磁盘管理中格式化、分区、挂载等操作步骤,希望这篇文章对大家有所帮助。 一、磁盘知识 1.磁盘含义 磁盘(Disk)是一种用于存储和读取数据的物理设备,它由一个或多个旋转的磁性盘片组成。这些盘片通常由…

递归算法学习

递归算法介绍 递归指的是函数或算法在执行过程中调用自身。在递归的过程中,程序会不断地将自身的执行过程压入调用栈中,直到满足某个条件结束递归调用并开始返回。递归算法常用于解决一些具有递归结构的问题,比如树、图、排序等。递归算法可以…

5.controller部署nova服务

nova 服务是 OpenStack service 计算服务,负责维护和管理云环境的计算资源; 例如: 接收客户端请求需要的计算资源; 确定实例在哪个物理机上创建; 通过虚机化的方式将实例启动运行等工作。 controller节点 在安装和配…

JAVA为什么要面向对象

JAVA是一个面向对象的语言 ok,我们开始,用了那么久的java,看了那么多代码,你是否懂得了面向对象,今天我们的第一个问题就是,为什么java要面向对象,要解释为啥要面向对象,我们首先要…

Python selenium自动化测试模型图解

1、线性测试 优势:每一个脚本都是完整独立的,每一个脚本对应一个测试用例 缺点:开发成本高,会有重复操作重复脚本;维护成本也高,修改重复操作的脚本时,要逐一进行修改。 2、模块化驱动测试 …

2023年计算机专业毕业实习报告最新

2023年计算机专业毕业实习报告最新篇1 一、实习基本情况 按照学校对毕业生的要求,为毕业后的工作和谋职打下良好的基础。我于X年X月来到山西柳林汇丰兴业同德焦煤有限公司进行为期X个月的实习。毕业实习让我们想起那句老话:“让学生赢在起跑线上。”在学…

验证性实验 - 线性回归

练习1:线性回归 介绍 在本练习中,您将实现线性回归并了解其在数据上的工作原理。 在开始练习前,需要下载如下的文件进行数据上传: ex1data1.txt -单变量的线性回归数据集ex1data2.txt -多变量的线性回归数据集 在整个练习中&a…

马斯克撕下美国的遮羞布,美企纷纷背刺,外媒:可怕的还在后面

马斯克访华造成的影响还在持续,随着更多美国企业家访华以及表明态度,可以说他们正撕下美国的遮羞布,今天的美国其实早已背离了当初他们所宣扬的价值,凸显出外强中干的本质。 二.美国企业家纷纷访华撕下美国遮羞布 美国一直都标榜贸…

某购房通小程序解密分析【2023.6.17】

声明 本文以教学为基准、本文提供的可操作性不得用于任何商业用途和违法违规场景。 本人对任何原因在使用本人中提供的代码和策略时可能对用户自己或他人造成的任何形式的损失和伤害不承担责任。 如有侵权,请联系我进行删除。 主要对小程序返回的加密结果进行解密的分析过程只…

如何在编程中中实现负载均衡和容错处理

什么是容错 容错是指系统(计算机、网络、云集群等)在其一个或多个组件发生故障时继续运行而不会中断的能力。 创建容错系统的目的是防止由单点故障引起的中断,确保任务关键型应用程序或系统的高可用性和业务连续性。 容错系统使用备份组件…

DAY26:回溯算法(一):回溯算法理论

课程链接:https://www.bilibili.com/video/BV1cy4y167mM/?spm_id_from333.788 什么是回溯法 回溯法 - OI Wiki (oi-wiki.org) 回溯法是一种经常被用在 深度优先搜索(DFS) 和 广度优先搜索(BFS) 的技巧。 其本质是…

Elasticsearch 基本使用(一)写入数据

写入数据 查询索引状态写入一条数据查询数据按id查询一条 类比 getById不按id查 写入官方测试数据 查询索引状态 GET _cat/indices写入一条数据 PUT/POST my_index/_doc/1 {"k": "test key" }my_index:索引名 _doc:文档类型&#…

css基础知识三:说说em/px/rem/vh/vw的区别?

一、介绍 传统的项目开发中,我们只会用到px、%、em这几个单位,它可以适用于大部分的项目开发,且拥有比较良好的兼容性 从CSS3开始,浏览器对计量单位的支持又提升到了另外一个境界,新增了rem、vh、vw、vm等一些新的计量…

2023年网络安全竞赛——网页渗透

网页渗透 任务环境说明:  服务器场景:Server2120  服务器场景操作系统:未知(封闭靶机)  用户名:未知 密码:未知 访问服务器的网站主页,猜测后台数据库中本网页中应用的库名称长度,将长度作为flag提交; 通过扫描发现靶机开放80端口,直接访问80 尝试输入一个1,…

Web安全信息收集之CMS指纹识别

1、CMS指纹识别 CMS(内容管理系统),又称整站系统或文章系统网站内容管理。用户只需要下载对应的CMS软 件包,部署搭建,就可以直接利用CMS,简单方便。但是各种CMS都具有其独特的结构命名规则和定 的文件内容,因此可以利用这些内容来获取CMS站点的具体软件CMS与版本 常见CMs: …

软件管理Linux

1. 获取程序包的途径 系统发行版的光盘或官方的服务器 http://mirrors.aliyun.comhttp://mirrors.sohu.comhttp://mirrors.163.com 项目官方站点第三方组织 Fedora-EPEL(推荐)搜索引擎: http://pkgs.org http://rpmfind.net http://rpm.pbon…

数据库第十章(数据库恢复技术)十一章(并发控制)

目录 1.事务 2.并发控制 1.事务 事务的特点:ACID 原子性 atom 一致性 consistent 隔离性 isolation 持久性 durable 故障的种类 1.事务内部故障 措施:采取redo重做和undo撤销技术 2.系统故障DBMS 措施:重启 3.介质故障 硬件损坏 4.计…