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

news2024/12/24 20:20:30

关于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.coloradmin.cn/o/1680766.html

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

相关文章

IDEA 每次启动都显示选择项目页面

IDEA版本:2021.3.3 打开 Settings > Appearance & Behavior > System Settings 取消勾选 Reopen projects on startup 然后下次启动 IDEA 会显示选择项目页面

论文阅读 - Anatomy of an AI-powered malicious social botnet

论文链接: https://arxiv.org/pdf/2307.16336.pdf 目录 摘要 1引言 2 相关工作 2.1 LLM驱动的网络威胁 2.2 LLM生成的内容检测 2.3 社交机器人检测 2.4 由 LLM 增强的机器人 3 Fox8僵尸网络的识别 4 特性 4.1 配置文件 4.2 社交网络 4.3内容类型 4.4放…

全新多语言海外抢单刷单系统源码 订单自动匹配 支持分组 代理后台

安装教程 测试环境:Nginx PHP7.0 MySQL5.6 config/database 修改数据库 设置运行目录public 伪静态thinkphp 后台登录地址:/admin 账号admin 密码admin123 前端出现报错 删除runtime文件夹得缓存文件即可 源码免费下载地址抄笔记 (chaobiji.cn)

数据结构——冒泡排序

懒猫老师-数据结构-(63)冒泡排序(起泡排序)_哔哩哔哩_bilibili 交换排序的一类 基本思想 两两比较相邻记录的关键码,如果反序则交换,直到没有反序的记录为止。 过程 有序区不断扩大,无序区不断减小

vllm docker部署qwen等大模型推理;api post调用访问

参考: https://docs.vllm.ai/en/latest/serving/deploying_with_docker.html https://hub.docker.com/r/vllm/vllm-openai https://blog.csdn.net/weixin_42357472/article/details/136165481 下载镜像: docker pull vllm/vllm-openai 镜像默认最后一层就是python -m vllm.…

ModuleNotFoundError: No module named ‘sklearn‘

ModuleNotFoundError: No module named sklearn 解决办法: pip install scikit-learn

在win10折腾Flowise:部署和尝试

Flowise 是一种低代码/无代码拖放工具,旨在让人们轻松可视化和构建 LLM 应用程序。 本地部署 操作系统: win10 由于网络、操作系统等各种未知问题,使用npm install -g flowise的方式,尝试了很多次,都没有部署成功&am…

Python 白底黑字图片去除红色水印

Python 白底黑字图片去除红色水印 import os from PIL import Imagedef remove_color(image_path, new_image_path):"""初始化:param image_path: 图片路径:param new_image_path: 新图片路径"""# 打开图片并转换为RGBA格式img Image.open(imag…

C语言例题42、打印金字塔

#include <stdio.h>void main() {int i, j;for (i 0; i < 5; i) {for (j 4; j > i; j--) {//输出空格printf(" ");}for (j 0; j < 2 * i 1; j) {//输出星号printf("* ");}printf("\n");} }运行结果&#xff1a; 本章C语言经…

【智能算法】清道夫优化算法(CFO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;W Zhang受到清道夫自然行为启发&#xff0c;提出了清道夫优化算法&#xff08;Cleaner Fish Optimization Algorithm, CFO&#xff09;。 2.算法原理 2.1算法思想 CF…

【JAVA SE】初识JAVA

✨✨欢迎大家来到Celia的博客✨✨ &#x1f389;&#x1f389;创作不易&#xff0c;请点赞关注&#xff0c;多多支持哦&#x1f389;&#x1f389; 所属专栏&#xff1a;JAVA 个人主页&#xff1a;Celias blog~ 目录 ​编辑 一、关于JAVA 1.1 JAVA语言简介 1.2 语言优势 1…

【Shell】shell编程之数组

目录 一、数组的概念 二、数组定义方法 三、数组 1.获取数组长度 2.获取数组数据列表 3.获取数组下标列表 4.读取某下标赋值 5.数组遍历 6.数组切片 7.数组替换 8.数组删除 四、数组追加元素 五、向函数传数组参数 ​编辑六、数组排序算法 1.冒泡排序 2.直接选…

Vue的学习 —— <路由与网络请求>

目录 前言 正文 一、初识路由 二、初识Vue Router 1、安装Vue Router 2、Vue Router基本使用 三、路由重定向 四、嵌套路由 前言 在之前的学习中了解到单页Web应用通常只有一个HTML页面&#xff0c;所有的组件展示和切换都在这个页面上完成。虽然我们可以通过动态组件…

数据密码机独特的安全性能

数据密码机&#xff0c;作为一种专用的信息安全设备&#xff0c;在现代社会的各个领域中都发挥着至关重要的作用。它以其独特的加密技术和安全性能&#xff0c;为数据的传输和存储提供了坚实的保护屏障。 首先&#xff0c;数据密码机的工作原理是基于复杂的加密算法。这些算法能…

【Javaer学习Python】2、Django的MVT设计模式,完成CRUD小应用

系列文章&#xff1a;学习Python Django的MVT设计模式由Model(模型), View(视图) 和Template(模板)三部分组成&#xff0c;分别对应单个app目录下的models.py, views.py和templates文件夹。它们看似与MVC设计模式不太一致&#xff0c;其实本质是相同的&#xff1b; 实践是检验学…

Leetcode2391. 收集垃圾的最少总时间

Every day a Leetcode 题目来源&#xff1a;2391. 收集垃圾的最少总时间 解法1&#xff1a;前缀和 收集垃圾的时间分为两部分&#xff1a; 垃圾车收拾垃圾的时间&#xff1a;垃圾车收拾一单位的任何一种垃圾都需要花费 1 分钟。三辆垃圾车行驶的时间&#xff1a;每辆垃圾车…

24HN逆向部分wp

24H&N逆向部分wp 菜鸡新手师傅wp&#xff0c;Re 5/9&#xff0c;记录一下qaq&#xff08;好久没写博客了&#xff0c;水一篇hh&#xff09; 最喜欢的逆向题 64位&#xff0c;进主函数之后直接看&#xff0c;要求输入第5位为i&#xff0c;然后后面依次相等&#xff0c;长…

线性系统(一)

线性系统&#xff08;一&#xff09; 1.什么是线性系统2.高斯消元法3.高斯-约旦消元法4.线性方程组解的结构 链接: 线性系统&#xff08;二&#xff09; 1.什么是线性系统 线性&#xff1a;未知数只能是一次方项 非线性: 同时&#xff0c;读者也可以通过作图来更直观地感受&…

HNU-算法设计与分析-作业1

算法设计与分析 计科210X 甘晴void 202108010XXX 前言 这个系列本来想只用一个博客搞定的&#xff0c;谁曾想CSDN对于大批量文字的在线编辑一塌糊涂&#xff0c;感觉走倒车了。只能分成几个博客分别来讲了。后续会有作业-23456。作业重要的是搞懂原因。 文章目录 算法设计与…

怎么把图片改成300dpi?照片dpi调整方法

在进行印刷设计时&#xff0c;例如制作海报、宣传册、名片、杂志等&#xff0c;通常要求图片具有高分辨率&#xff0c;将图片分辨率设为300dpi可以确保图像在印刷过程中保持细节和清晰度&#xff0c;但是修改图片分辨率的方法有哪些呢?今天小编整理了几个关于改变图片分辨率的…