0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Node.js 内存泄漏问题初探

张康康 2018-11-01 13:39 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

作者 | 后端Team朱捷峰

整理 | 包包

V8 垃圾回收机制

事实上,我们平时在写 Node.js 的时候很少去关心内存问题,那是因为 Node.js 对 Google V8 进行封装,底层的垃圾收回机制都交给 V8 处理。大部分时候,是不会有内存问题的。相对于 C/C++ 这类需要自己管理内存的语言,Node.js 有更加平滑的学习曲线,这也是 Node.js 最大的优势之一。但是也总有意外情况,可能导致 Node.js 进程内存泄漏。

那么如何避免我们的 Node.js 程序出现内存泄漏的情况呢?我们先来了解下 V8 内存管理机制。

一个进程通常是通过在内存中分配空间来体现的,这个空间我们称之为 Resident Set(常驻空间)。V8 将内存分为了以下几块:

• 代码区:实际正在运行的代码

• 栈区:包含了所有的值类型(数字、布尔值等)、指向存储在堆区的对象指针、定义程序控制流的指针

• 堆区:专门用来存储引用类型的内存区域,比如对象、字符串和闭包


在 Node.js 中,我们可以通过调用process.memoryUsage() 方法来来查询内存使用情况。该函数返回值如下:

memory usage

{

rss: 4935680,

heapTotal: 1826816,

heapUsed: 650472,

external: 49879

}

以上数值以字节为单位

• rss:表示 Resident Set 的大小

• heapTotal:表示堆的总大小

• heapUsed:表示堆的实际使用大小

• external:表示 V8 管理的绑定到 JavaScript 对象的 C++ 对象的大小

我们知道在 Node.js 的运行时中,JavaScript 是由 V8 编译成可执行的机器码。运行时的数据结构是由 V8 来管理的,我们能做的很有限。通过 JavaScript 我们是没法做到分配内存和释放内存的。

V8 的垃圾回收算法实现还是很复杂的,感兴趣的同学可以参考:http://newhtml.net/v8-garbage-collection/。但是我们仍然可以把原理简单抽象:如果一个内存片段没有被任何地方引用,我们可以假设它不再会被用到,那么该内存片段可以被释放。


上图表示在内存中各个对象的引用情况,只有当红球对象不再被任何对象引用的时候,它才能被回收。

异常情况

既然 V8 会进行垃圾回收,那我们为什么还要关心内存情况呢?

理想情况,内存占用会保持在一个相对稳定的范围:


实际上,我们仍然可能会看到内存占用升高的情况:


V8 垃圾回收机制尽可能地回收和释放内存,但是每次执行垃圾回收以后,内存占用仍然持续上升,这明显就是内存泄漏了。

制造内存泄漏

有一些很明显的情况会导致内存泄漏:1、比如将每位访客的 IP 记录在 global 上存储数组上;2、再比如著名的“ 沃尔玛内存泄漏事件”,它是由 Node.js 核心代码中一个遗漏的声明引发的血案,工程师们花了好几个星期去排查并最终得以解决。

在这篇文章里,我们就不一一列举所有可能产生问题的错误情况。我们来看一下一个难以排查的情况,代码很简单,你可以自己运行调试:

memory leak demo

const express = require('express');

const app = express();

const port = 3000;

let theThing = null;

const replaceThing = function () {

let originalThing = theThing;

let unused = function () {

if (originalThing)

console.log("hi");

};

theThing = {

longStr: new Array(1000000).join('*'),

someMethod: function () {

console.log(someMessage);

}

};

};

app.get('/leak', (req, res) => {

replaceThing();

let memoryInfo = JSON.stringify(process.memoryUsage());

console.log(memoryInfo);

res.send(memoryInfo);

})

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

初看的话,这段代码没啥问题。我们可以想象 theThing 在每次调用 replaceThing() 时会被重写。问题就在于,someMethod 有闭包作用域作为上下文,这就意味着当调用 someMethod 时,unused 是可见的。虽然实际上 unused 并没有被调用,但是它却阻止了 V8 垃圾回收机制对 originalThing 进行回收。这就是我们平时所说的“循环引用”:


既然找到问题所在,那么如何解决呢?答案很简单,我们只要切断循环引用就可以了,这里我们只需要在 replaceThing 这个方法最后加入 theThing = null。

针对这个问题,我们还可以通过 ESLint 的 no-unused-vars 规则来避免定义了但是未使用的变量,这样可以减少循环引用的可能性。

排查问题

理解了垃圾回收的原理,那么我们平常在码代码的时候也要注意避免循环引用的情况出现。但是就像上面这种情况,有时候就是防不胜防。那么遇到问题的时候,我们应该如何排查呢?

推荐一下我写的一个小工具 heapsnapshot.js ,可以获取生成堆的快照信息,如下图:


然后利用 Chrome 开发者工具,Memory 来做具体分析:


请选择相邻的3个堆快照文件,导入 Memory 分析工具中,如下图:


第一步,先选择 Profiles 中的第二个文件,然后筛选 Objects 选项选择“Objects allocated between 1539255057342 and 1539255076968 ”,然后在 Constructor 中进行具体的分析 。


第二步,同理对第二个和第三个文件进行对比分析。找到两次分析都出现过的元素,重点排查,定位到具体的问题代码,再做修改。


第三步,重复上述过程,检查内存泄漏问题是否解决。

以上只是对 Node.js 内存问题的一个初步探讨,感兴趣的话推荐大家去看下 V8 垃圾回收的原理。平常我们在编码的时候也要注意尽量避免产生循环引用,但是如果遇到了也不要担心,可以通过上面的步骤排查解决。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内存
    +关注

    关注

    9

    文章

    3174

    浏览量

    76147
  • NODE.JS
    +关注

    关注

    1

    文章

    49

    浏览量

    33899
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    WebGL/Canvas 内存泄露分析

    在构建高性能、长周期运行的 WebGL/Canvas 应用(如 3D 编辑器、数据可视化平台)时,内存管理是一个至关重要且极具挑战性的课题。 开发者通常面临的内存泄漏问题,其根源远比简单
    的头像 发表于 10-21 11:40 194次阅读
    WebGL/Canvas <b class='flag-5'>内存</b>泄露分析

    at_device 包 ml307长时间运行有内存泄漏问题怎么解决?

    使用 at_device 包中的 ml307 包长时间运行有大量内存泄漏问题,大概涨了20K,求助解决。
    发表于 09-24 07:41

    【M-K1HSE开发板免费体验】M-K1HSE开发板构建HELLO WORLD页面

    工程存放的位置,其他参数保持默认设置即可,点击【finish 】。 其中Node用来配置当前工程运行的Node.js版本,可选择使用已有的Node.js或下载新的Node.js版本 4
    发表于 07-15 04:31

    在OpenVINO™ C++代码中启用 AddressSanitizer 时的内存泄漏怎么解决?

    在 OpenVINO™ C++代码中启用 AddressSanitizer 时遇到内存泄漏: \"#0 0xaaaab8558370 in operator new(unsigned
    发表于 06-23 07:16

    HarmonyOS5云服务技术分享--ArkTS开发函数

    等打包 ✅ 支持Node.js 14.x/18.x和Java 1.8 ✅ 支持HTTP触发器调用 ✅ 持续开发调试一条龙 ?️ 准备工作: 安装AGCLI工具(华为应用分发服务命令行工具) 准备测试
    发表于 05-22 17:29

    HarmonyOS5云服务技术分享--ArkTS开发Node环境

    ✨ 你好呀,开发者小伙伴们!今天我们来聊聊如何在HarmonyOS(ArkTS API 9及以上)中玩转云函数,特别是结合Node.js和HTTP触发器的开发技巧。文章会手把手带你从零开始,用最接地
    发表于 05-22 17:21

    HarmonyOS5云服务技术分享--云函数创建配置指南

    指南 ​​ZIP包上传失败​​:检查文件结构!Node.js/Python的入口文件必须放根目录,Java的包路径要和代码一致。 ​​内存不足报错​​:函数处理大文件时,内存选4GB更保险
    发表于 05-22 17:08

    keithley 2600系列labiew vi中配置测量功能中的node in 和node out具体功能是什么?

    图中的node in和node out的具体作用是什么呢?新手刚刚接触labview和源表,不太明白其具体功能
    发表于 05-12 10:11

    KaihongOS操作系统:开发环境搭建

    ”。 步骤 2选择 Do not import settings,点击 OK。 步骤 3安装 Node.js 与 ohpm。可以指定本地已安装的 Node.js 或 ohpm 路径位置;如果本地 没有合适
    发表于 04-23 07:27

    在树莓派上构建和部署 Node.js 项目

    探索在RaspberryPi上构建和部署Node.js项目的最佳实践。通过我们的专业提示和技巧,克服常见挑战,使您的项目顺利运行。去年圣诞节,我收到了一份极其令人着迷的礼物,它占据了我许多周末的时间
    的头像 发表于 03-25 09:44 521次阅读
    在树莓派上构建和部署 <b class='flag-5'>Node.js</b> 项目

    仅仅使用代码,就能点亮树莓派的 GPIO 世界

    RaspberryPi的一个流行应用是构建Web服务器。为此,我们可以使用不同的技术,如Python、Node.JS甚至PHP。由于RaspberryPi的绝大多数脚本都是用Python编写的,因此
    的头像 发表于 03-25 09:31 495次阅读
    仅仅使用代码,就能点亮树莓派的 GPIO 世界

    【干货】什么是Node-RED?一文带你了解!

    一什么是Node-RED?首先我们来认识一下什么是Node-RED。Node-RED是一个基于Node.js的开源可视化编程工具,于2013年由IBM公司推出。它可以通过浏览器的图形化
    的头像 发表于 03-13 19:32 1564次阅读
    【干货】什么是<b class='flag-5'>Node</b>-RED?一文带你了解!

    使用OpenVINO™进行推理时的内存泄漏怎么解决?

    使用 OpenVINO™ 进行推理时,内存会随着时间的推移而增加,并导致程序崩溃。
    发表于 03-06 08:29

    内存泄漏检测工具Sanitizer介绍

    内存泄漏,我们经常会遇到,如何检测内存泄漏,除了我们之前讲过的 valgrind,还可以使用 gcc 自带的工具 sanitizer。
    的头像 发表于 03-01 14:52 1496次阅读

    Bun 1.2震撼发布:全力挑战Node.js生态的JavaScript运行时新星

    了与 Node.js 的兼容性,还为开发者带来了内置的数据库支持和云服务集成能力,进一步强化了其“全能工具包”的定位。Node.js 兼容性获得突破性进展在此次更新中,最引人注目的是 Bun 在 Node.js 兼容性
    的头像 发表于 01-24 10:42 1358次阅读
    Bun 1.2震撼发布:全力挑战<b class='flag-5'>Node.js</b>生态的JavaScript运行时新星