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本身不同的语言中。这样做的优点是可以减少应用程序再次运行时所需的解析和编译时间,但是缺点是它需要额外的步骤,因此可能会增加开发时间和复杂性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

tensorrtx-yolov5-v6.0部署在windows系统

前言:最近几天一直在搞这个东西,现在跑通了,为了以后自己看和帮助他人,就记录一下。虽然是跑通了但是觉得怪怪的,感觉不是自己想要的效果,另外这个只能检测图片,不能摄像头实时监测(我暂时没找到…

python中cv2,等等如何修改为中文字体显示,这里以人脸表情识别中文标签为例

中文字体显示 首先下载字体包部署字体包代码实现部分 想必大家在使用python过程中都会遇到,想要显示中文的时候,但是py基本上都是英文字体,下面我将给大家提供一个比较好的解决方案: 首先下载字体包 方法: 我使用的是…

SuperBox设计出图的效率提升!新增内门自动开孔和垫高支架图纸输出功能

越来越多的配电箱项目要求带内门,内门不仅可以有效减少外界灰尘、异物进入配电箱内部,保障配电箱正常运行,还能够隔离操作人员意外触摸导电部件,减少触电事故的发生。但是配电箱在配置内门后,会给设计带来更多的要求&a…

Linux虚拟主机cPanel重置密码

我使用的Hostease的Linux虚拟主机产品默认带普通用户权限的cPanel面板,这边自购买后一直未重新设置过cPanel面板的密码,但是了解到要定期重置一下cPanel面板的密码,以确保主机数据安全,因此想要进行重置cPanel面板的密码&#xff…

Leecode热题100---3:无重复字符的最长子串

题目&#xff1a;给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串的长度。 C&#xff1a; 指针法&#xff0c;使用at读取字符串中的值&#xff1b; #include <iostream> #include <string> #include <vector> #include <windows.…

数据结构【顺序表】

文章目录 1.顺序表的概念线性表物理结构逻辑结构 2.顺序表的分类2.1静态顺序表2.2动态顺序表 3.顺序表接口的实现头文件(SQList.h)如下源文件初始化顺序表销毁顺序表插入扩容尾插头插 封装扩容函数删除尾删头删 查找元素在指定位置前插入数据情况一(指定的位置不是首元素)情况二…

淘宝扭蛋机小程序开发:探索未知的惊喜世界

一、引言 在这个充满无限可能的数字时代&#xff0c;每一次点击都可能带来意想不到的惊喜。淘宝扭蛋机小程序&#xff0c;正是为了满足您对惊喜的渴望&#xff0c;将扭蛋的趣味与购物的便捷完美结合&#xff0c;带您进入一个充满未知与乐趣的惊喜世界。 二、产品介绍 淘宝扭…

Redis教程(二):Redis在Linux环境下的安装

Linux环境下安装&#xff1a; 下载地址&#xff1a;Downloads - Redis 安装步骤&#xff1a; 下载得到一个 tar.gz 压缩文件 上传到Linux的/opt/soft目录&#xff0c;使用以下命令解压 tar -zxvf redis-6.2.14.tar.gz Linux安装基本环境gcc&#xff0c;安装命令 yum insta…

Encoder——Decoder工作原理与代码支撑

神经网络算法 &#xff1a;一文搞懂 Encoder-Decoder&#xff08;编码器-解码器&#xff09;_有编码器和解码器的神经网络-CSDN博客这篇文章写的不错&#xff0c;从定性的角度解释了一下&#xff0c;什么是编码器与解码器&#xff0c;我再学习笔记补充的时候&#xff0c;讲一下…

什么是网络端口?为什么会有高危端口?

一、什么是网络端口&#xff1f; 网络技术中的端口默认指的是TCP/IP协议中的服务端口&#xff0c;一共有0-65535个端口&#xff0c;比如我们最常见的端口是80端口默认访问网站的端口就是80&#xff0c;你直接在浏览器打开&#xff0c;会发现浏览器默认把80去掉&#xff0c;就是…

dfs记忆化搜索,动态规划

动态规划概念&#xff1a; 给定一个问题&#xff0c;将其拆成一个个子问题&#xff0c;直到子问题可以直接解决。然后把子问题的答案保存起来&#xff0c;以减少重复计算。再根据子问题的答案反推&#xff0c;得出原问题解。 821 运行时间长的原因&#xff1a; 重复大量计算…

Cadence 16.6 绘制PCB封装时总是卡死的解决方法

Cadence 16.6 绘制PCB封装时总是卡死的解决方法 在用Cadence 16.6 PCB Editor绘制PCB封装时候&#xff0c;绘制一步卡死一步&#xff0c;不知道怎么回事儿&#xff0c;在咨询公司IT后&#xff0c;发现是WIN系统自带输入法的某些热键与PCB Editor有冲突&#xff0c;导致卡死。 …

融资融券最低利率4.0!,融资融券利息计算公式,怎么开通?

融资融券的费率&#xff1a; 融资融券的费率主要包括融资利率和融券费率&#xff0c;这些费率的高低主要取决于证券公司的成本、政策倾向以及投资者的资金量大小。 融资利率方面&#xff0c;多数券商的优惠融资利率在5.5%到7.5%之间&#xff0c;与券商的成本和政策有关。一些…

带你了解AI大模型的前世今生

过去&#xff0c;开发者用代码来改变世界&#xff0c;未来&#xff0c;自然语言将成为通用的编程语言。大模型是如何成功的&#xff1f;有哪些应用&#xff1f;现在如何入局&#xff1f;一个全知全能的大模型能适配一切吗&#xff1f;在这个 AI 时代&#xff0c;什么样的工具才…

请收好,这份思科备考攻略很细节

对于网络工程师来说&#xff0c;思科认证无疑是一块金字招牌。它不仅代表着专业技能&#xff0c;更是职业发展的加速器。 今天我们不聊选思科认证还是华为认证&#xff0c;只能说是各有各的好&#xff0c;如果你已经选择了思科认证&#xff0c;那么这份备考攻略将为你提供一些实…

JavaScript异步编程——11-异常处理方案【万字长文,感谢支持】

异常处理方案 在JS开发中&#xff0c;处理异常包括两步&#xff1a;先抛出异常&#xff0c;然后捕获异常。 为什么要做异常处理 异常处理非常重要&#xff0c;至少有以下几个原因&#xff1a; 防止程序报错甚至停止运行&#xff1a;当代码执行过程中发生错误或异常时&#x…

国网1376.1主站与采集终端通信协议和国网1376.2集中器本地通信模块接口协议报文解析工具

本文分享一个国网1376.1主站与采集终端通信协议的报文解析工具&#xff0c;同时本报文解析软件也支持国网1376.2集中器本地通信模块接口协议的报文解析。 下载链接: https://pan.baidu.com/s/1ngbBG-yL8ucRWLDflqzEnQ 提取码: y1de 主界面如下图所示&#xff1a; 同时本软件自…

继承,多态,封装以及对象的打印

前言&#xff1a; 我们都知道Java是一种面向对象的编程语言&#xff0c;面向对象语言的三大特性就是继承&#xff0c;多态&#xff0c;封装&#xff0c;而这些特性正好的Java基础的一个主体内容。在学到这之前&#xff0c;我们肯定已经学习过了类和对象&#xff0c;所以这部分…

关于FIFO Generator IP和XPM_FIFO在涉及位宽转换上的区别

在Xilinx FPGA中&#xff0c;要实现FIFO的功能时&#xff0c;大部分时候会使用两种方法&#xff1a; FIFO Generator IP核XPM_FIFO原语 FIFO Generator IP核的优点是有图形化界面&#xff0c;配置参数非常直观&#xff1b;缺点是参数一旦固定&#xff0c;想要更改的化就只能重…

幻兽帕鲁Palworld服务器手动部署

目录 帕鲁官方文档手动安装steamcmd通过steamcmd安装帕鲁后端客户端连接附录&#xff1a;PalServer.sh的启动项附录&#xff1a;配置文件 帕鲁官方文档 https://tech.palworldgame.com/ 手动安装steamcmd 创建steam用户 sudo useradd -m steam sudo passwd steam下载steamc…