NodeJS V8引擎内存和垃圾回收器

关于max_old_space_size

在这里插入图片描述
max_old_space_size参数用于指定V8引擎的老生代内存的最大大小。通过增加max_old_space_size参数的值,我们可以提供更多的内存给V8引擎,从而提高应用程序的性能和稳定性。

既然提到了老生代,就不得不提下什么是垃圾,以及与之密切绑定的垃圾回收机制。

什么是垃圾

程序运行过程中会用到一些数据,这些数据被放在堆栈中,在程序运行结束后,这些不再使用的数据,就是垃圾。

为什么需要GC

GC:Garbage Collection,垃圾回收器。

程序运行需要使用内存,内存的两个分区:栈区和堆区。
● 栈区是线性的队列,随着函数运行结束自动释放。于JS而言,栈用于存放JS中的基本数据类型和引用类型的指针。空间是连续的,增加和删除只需要移动指针,操作速度非常快。同时,栈的空间又是有限的,当栈满了,就会抛出一个内存溢出错误
● 堆区是自由的动态内存空间,堆内存可以手动分配释放,也可以由垃圾回收器自动分配释放。于JS而言,堆用于存放JS中的引用类型

软件开发初期,或者有些语言在处理堆内存的时候,都是手动操作分配和释放,比如C、C++。虽然能精准操作内存,但是开发效率也随之低下,也很容易出现内存操作不当的情况。

随着技术发展,高级语言(Java、Node)都不需要开发者手动操作内存,程序语言会自动分配和释放空间,因此诞生了GC,GC的作用就是帮助程序释放和整理内存。这样就使得开发者大部分情况下不需要关注内存本身,可以更加聚焦业务开发。

堆空间分类

堆区才需要垃圾回收,重点!!!
堆内存的设计与GC的设计是紧密相关的,在NodeJS中,GC采用分代策略,分为新生代和老生代,内存数据大都在这两个区域里。
在这里插入图片描述

新生代 new space

新生代内存用于存放一些生命周期比较短的对象数据。

老生代 old space

老生代内存存放一些生命周期较长的对象数据。

大对象空间 large object space

默认情况下超过256K的对象会直接在大对象空间创建,并且不会移动到其他空间。

运行时代码空间 code_space

用于存放JIT(即时编译)已编译的代码,这是唯一有执行权限的内存。

map空间 Map space

用于存储用于JavaScript对象的元信息和其他内部数据结构,比如Map和Set对象。

新生代的垃圾回收

新生代内存用于存放一些生命周期比较短的对象数据。新生代分为2个区域,对象区域(from空间)和对象(to空间),两个区域相互切换。

当对象首次创建后它们被分配到from空间,它的年龄就是1,当from空间不足或者超过一定大小数量之后,就会触发Minor GC(采用复制算法),此时,GC 会暂停应用程序的执行(STW,stop-the-world),标记(from空间)中所有活动对象,然后将它们整体移动到另一个空闲空间(to空间)中。最后原本的 from 空间的内存会被全部释放而变成空闲空间,两个空间就完成 from 和 to 的对换,复制算法是牺牲了空间换取时间的算法。

新生代的空间更小,所以此空间会更加频繁地触发GC,同时也是因为扫描的空间小,GC的性能消耗也更小,GC的执行时间也更短。

每当一次Minor GC完成,存活的对象年龄就+1,经历过多次Minor GC还存活的对象(年龄大于N),一般to空间大于75%,将会被移动到老生代内存池中。

老生代的垃圾回收

老生代内存是一个大的内存池,用于存放一些生命周期比较长的对象数据。Old Space 使用**标记清除(Mark-Sweep)标记压缩算法(Mark-Compact)**的方式进行垃圾回收。它的一次执行叫做Mayor GC。当老生代中的对象占满一定比例时,即存活对象与总对象的比例超过一定的阈值,就会触发一次标记清除和标记压缩。

因为它的空间更大,所以此空间GC执行时间也更长,频率相对新生代更低。如果老生代执行完GC后,空间还是不足,V8就会从系统中申请更多内存。

可以手动执行global.gc()方法,设置不同的参数,主动触发GC。需要注意的是,默认情况下,NodeJS是禁用垃圾回收的,如果要启用垃圾回收,可以通过启动 Node.js 应用程序时添加 --expose-gc 参数来开启,例如:

node --expose-gc xxx.js

默认情况下,执行gc报错:
在这里插入图片描述
在这里插入图片描述

开启GC后,不报错:
在这里插入图片描述

Mark-Sweep(标记清除)

Mark-Sweep分为两个阶段:标记和清除。Mark-Sweep在标记阶段遍历(深度优先遍历)堆中的所有对象,并标记活着的对象,然后进入清除阶段。在清除阶段,只清除未被标记的对象。

V8 采取的是黑色和白色来标记数据,垃圾收集之前,会把所有的数据设置为白色,用来标记所有的尚未标记的对象,然后会从 GC 根出发,以深度优先的方式把所有的能访问到的数据都标记为黑色,遍历结束后黑色的就是活的数据,白色的就是可以清理的垃圾数据。

● 由于标记清除只清除死亡对象,而死亡对象在老生代中占用的比例很小,所以效率较高
● 标记清除有一个问题就是进行一次标记清除后,内存空间往往是不连续的,会出现很多的内存碎片。如果后续需要分配一个需要内存空间较多的对象时,如果所有的内存碎片都不够用,就会出现内存溢出的问题
在这里插入图片描述

Mark-Compact(标记压缩)

Mark-Compact是为了解决内存碎片的问题,标记压缩是在标记清除的基础上进行修改,将其清除的阶段变为紧缩极端。在整理的过程中,将活着的对象像内存的一端移动,移动完成后,直接清理掉边界外的内存。V8也会根据一定的逻辑,释放一定空闲的内存还给系统。
● 由于在紧缩过程中涉及对象的移动,所以效率并不是太好
● 但是能保证不会生成内存碎片,一般10次标记清除会伴随一次标记压缩
在这里插入图片描述

V8新老分区大小

老生代区分

在v12.x之前,为了保证 GC 的执行时间保持在一定范围内,V8 限制了最大内存空间,设置了一个默认老生代内存最大值,64位系统中为大约1.4G,32位为大约700M,超出会导致应用崩溃。
如果想加大内存,可以使用 --max-old-space-size 设置最大内存(单位:MB)

node --max_old_space_size=2048 test.js

如果设置的太小,会触发OOM(out of memory)
在这里插入图片描述

在Node版本v12.x之后,V8 将根据可用内存分配老生代大小,也可以说是堆内存大小,所以并没有限制堆内存大小。以前的限制逻辑,其实不合理,限制了 V8 的能力,总不能因为 GC 过程消耗的时间更长,就不让我继续运行程序吧,后续的版本也对 GC 做了更多优化,内存越来越大也是发展需要。
如果想要做限制,依然可以使用 --max-old-space-size 配置, v12 以后它的默认值是0,代表不限制。

新生代区分

新生代中的一个 semi-space 大小 64位系统的默认值是16M,32位系统是8M,因为有2个 semi-space,所以总大小是32M、16M。
可以使用–max-semi-space-size 设置新生代 semi-space 最大值,单位为MB。
此空间不是越大越好,空间越大扫描的时间就越长。这个分区大部分情况下是不需要做修改的,除非针对具体的业务场景做优化,谨慎使用。

查看使用的V8引擎版本

n可以管理多个node版本,可以使用指定的node版本执行代码

n use 11.10.0 test.js
n use 20.12.1 test.js

在这里插入图片描述

内存指标

v8.getHeapStatistics()

查看 v8 堆内存信息,查询最大堆内存 heap_size_limit

// 查看堆空间的统计信息,单位是B字节
const v8 = require('v8');
function printHeapSpaceStats() {const heapStats = v8.getHeapStatistics();console.log(heapStats);
}
printHeapSpaceStats();

● total_heap_size:V8 引擎可以为堆内存分配的总大小。
● total_heap_size_executable:V8 引擎可以为堆内存分配的可执行代码的大小。
● total_physical_size:当前堆内存的物理占用大小(包括空闲区域)。
● total_available_size:V8 引擎能够分配的内存大小。
● used_heap_size:V8 引擎当前使用的堆内存大小。
● heap_size_limit:V8 引擎能够分配的最大内存大小。
在这里插入图片描述

不同版本node下,堆内存大小不同:
在这里插入图片描述
大部分情况下 heap_size_limit 的默认值是系统内存的一半。但是如果超过这个值且系统空间足够,V8 还是会申请更多空间。

process.memoryUsage()

可以查看内存使用情况。除此之外,os模块中的totalmem()和freemem()方法也可以查看内存使用情况。
在这里插入图片描述

rss是resident set size的缩写,即进程的常驻内存部分。进程的内存总共有几部分,一部分是rss,其余部分在交换区(swap)或者文件系统(filesystem)中。

heapTotal和heapUsed对应的是v8的堆内存信息,heapTotal是堆中总共申请的内存量,heapUsed表示目前堆中使用中的内存量,单位都是字节。

开启打印GC事件

import os from 'node:os';
let len = 1_000_000;
const entries = new Set();function addEntry() {const entry = {timestamp: Date.now(),memory: os.freemem(),totalMemory: os.totalmem(),uptime: os.uptime()};entries.add(entry);
}function summary() {console.log(`Total: ${entries.size} entries`);
}// execution
(() => {while (len > 0) {addEntry();process.stdout.write(`~~> ${len} entries to record\r`);len--;}summary();
})();

开启打印GC事件:

node --expose-gc script.mjs

打印堆空间的详细情况:

node --trace_gc_verbose script.mjs

每次GC事件的详细信息,GC类型,各种时间消耗,内存变化等

node --trace_gc_nvp script.mjs

内存快照

const { writeHeapSnapshot } = require('node:v8');
v8.writeHeapSnapshot()

打印快照,将会STW,服务停止响应,内存占用越大,时间越长。此方法本身就比较费时间,所以生成的过程预期不要太高,耐心等待。
此 API 会生成一个 .heapsnapshot 后缀快照文件,可以使用 Chrome 调试器的“内存”功能,导入快照文件,查看堆内存具体的对象数和大小,以及到GC根结点的距离等。也可以对比两个不同时间快照文件的区别,可以看到它们之间的数据量变化。
在这里插入图片描述
在这里插入图片描述

JS编译

JS是一种解释型的语言,可以在很多地方进行编辑。

浏览器中编译

最初JS在浏览器中运行时,是由JS引擎逐行进行解析和执行的。由于JS代码的普及,浏览器厂商开始将编译作为提高JS性能的手段。许多现代浏览器都将JS代码编译为二进制代码,并进行缓存,以便下次再次使用,这样可以减少解析和编译过程需要的时间,从而加快JS代码的运行速度。

Node.js中编译

Node.js是一种基于Chrome V8引擎的JS运行环境,可以在服务器端运行JS应用程序。Node.js采用与浏览器类似的方式运行JavaScript代码,即JavaScript代码进入Node.js运行时,首先被解析为抽象语法树,然后转换为字节码,最后被编译为机器代码。由于Node.js不像浏览器一样面对浏览器不同的环境,因此它可以开放更多的提高JavaScript性能的方式。

JIT(即时编译)

JIT(即时编译)是一种将字节码或解释的代码直接编译成机器代码的技术。在阅读JavaScript代码时,JIT编译器可能会发现代码中的热点,然后对这些热点进行编译,以使其更快地执行。由于JIT编译器可以在运行时不断改进编译过程,因此,它的性能甚至可以超过一些事先编译的语言。

预编译

预编译会在生产环境之前将JS代码静态地编译到一种与JavaScript本身不同的语言中。这样做的优点是可以减少应用程序再次运行时所需的解析和编译时间,但是缺点是它需要额外的步骤,因此可能会增加开发时间和复杂性。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

项目管理重点及笔记【太原理工大学24年】

为了自己复习写的,可能会有不清楚,可以选择性看可以讨论,但别骂我写的什么烂东西,,,(电脑看排版更好看些) 目录 一、IT项目管理概述 二、IT项目组织环境与管理过程 三、整体管理 四、…

5W 3KVAC隔离 宽电压输入 AC/DC 电源模块——TP05AL系列

TP05AL系列产品是一款经济型开板式开关电源,输出功率为5W,具有可靠性高、小体积、性价比高等特点,广泛用于工控和电力仪器、仪表、智能家居等相关行业。

LeetCode-1953. 你可以工作的最大周数【贪心 数组】

LeetCode-1953. 你可以工作的最大周数【贪心 数组】 题目描述:解题思路一:贪心,关键点是找到(工作最长时间的工作longest )与(剩余其他工作加起来的时间总和rest )的关系。解题思路二:证明解题思路三:简化 题目描述&am…

PM入门必备| 怎么写产品分析报告?

​小陪老师,产品经理是做些什么的呢?我去面试应该准备些什么呢? A: 首先要分清产品经理的类型,产品的面试需要准备的一般有Axure原型,需求文档,产品分析报告等,有些甚至需要展示项目经验。 tea…

MySQL性能飞跃:揭秘高效数据库优化的黄金法则

MySQL数据库性能优化是一个涉及多个层面的复杂过程,需要根据具体的应用场景、数据结构和查询模式等因素来定制优化方案。以下是针对不同场景下MySQL数据库性能优化的详细指南。 建库建表阶段的优化 1、存储引擎的合理选择 在创建数据库时,选择合适的存…

慧天卓特:2023-2024年高温“炙烤”非洲,FYDI干旱指数监测分析

【本案例分析,展示了慧天卓特FYDI-2.0干旱监测产品的大范围持续精准监测能力】 研究背景 2023年,全球气候变化对非洲的影响尤为严重。尚未走出持续三年旱灾的非洲之角国家又遭洪灾重创,数十万人流离失所。尤其是下半年,洪水造成…

如何修改照片的dpi值?在线图片更改dpi快捷方法

不少小伙不知道如何去改变图片分辨率,因为能用的到分辨率dpi的地方也不多,但是当我们在求职、考公或者是打印照片的时候,就经常看到会要求图片分辨率为300dpi以上了,本文将分享一款图片处理工具,它能快速完成修改图片分…

软件工程期末复习(4)软件过程

软件过程 软件过程是为了获得高质量软件所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤。 ISO 9000对过程的定义: 使用资源将输入转化为输出的活动所构成的系统。 瀑布模型: 瀑布模型的特点: 阶段间具有顺序性和依赖性 必须…

聚观早报 | OPPO K12x开启预售;苹果iPad Air 6开售

聚观早报每日整理最值得关注的行业重点事件,帮助大家及时了解最新行业动态,每日读报,就读聚观365资讯简报。 整理丨Cutie 5月16日消息 OPPO K12x开启预售 苹果iPad Air 6开售 moto X50 Ultra将发布 谷歌宣布为安卓引入新功能 谷歌Pixe…

基于51单片机的非接触式无线红外测温

基于51单片机的无线红外测温 (程序+原理图+设计报告) 功能介绍 具体功能: 1.采用红外温度传感器测温并用LCD1602显示; 2.按键为启动按键、保存按键、显示数据按键,可以实现对温度数值的控制…

英语学习笔记11——It this your shirt?

It this your shirt? 这是你的衬衫吗? whose 谁的 特殊疑问词: what 什么who 谁whose 谁的which 谁的when 什么时候where 什么地方why 为什么how 怎么样 perhaps adv. 大概 【注意拼写】 catch v. 抓住 口语:Catch! 接着!v.…

创新指南 | 企业AI战略实施方案探讨(下):如何基于AI重构业务流程并落地实施

人工智能(AI)的浪潮已经席卷全球,成为推动现代企业发展的强大动力。AI技术不仅提升了企业的运营效率,还催生了新的商业模式和市场机会。本文将深入探讨AI的革新性应用案例,并提供一套企业落地AI的具体实施方案&#xf…