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 垃圾回收的原理。平常我们在编码的时候也要注意尽量避免产生循环引用,但是如果遇到了也不要担心,可以通过上面的步骤排查解决。


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

    关注

    8

    文章

    2767

    浏览量

    72753
  • NODE.JS
    +关注

    关注

    1

    文章

    46

    浏览量

    32683
收藏 人收藏

    评论

    相关推荐

    C语言内存泄漏问题原理

    内存泄漏问题只有在使用堆内存的时候才会出现,栈内存不存在内存泄漏问题,因为栈
    发表于 03-19 11:38 162次阅读
    C语言<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>问题原理

    鸿蒙 Harmony 工具篇

    平台做的自定义 IDE。因此,对于 Java 开发者而言,DevEco 只是“在熟悉中带有一点点陌生”。 安装 Node.js 在安装好 DevEco 之后,首次点开需要开发者设置 Node.js
    的头像 发表于 01-30 15:00 353次阅读
    鸿蒙 Harmony 工具篇

    内存溢出与内存泄漏:定义、区别与解决方案

    内存溢出与内存泄漏:定义、区别与解决方案  内存溢出和内存泄漏是计算机科学中常见的问题,在开发和
    的头像 发表于 12-19 14:10 1253次阅读

    Nodejs的CommonJS规范实现原理

    Node.js 是一个基于 ChromeV8 引擎的 JavaScript 运行环境,使用了一个事件驱动、非阻塞式 I/O 模型,让 JavaScript 运行在服务端的开发平台
    的头像 发表于 11-25 10:21 266次阅读
    Nodejs的CommonJS规范实现原理

    线程内存泄漏问题的定位

    记录一个关于线程内存泄漏问题的定位过程,以及过程中的收获。 1. 初步定位 是否存在内存泄漏:想到内存
    的头像 发表于 11-13 11:38 321次阅读
    线程<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>问题的定位

    内存泄漏会产生哪些后果

    内存泄漏原因 内存泄漏在C/C++这种不带GC(Garbage Collection)的语言里,是一个经常发生的问题。因为没有GC,所以分配的内存
    的头像 发表于 11-10 15:06 392次阅读
    <b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>会产生哪些后果

    内存泄漏如何避免

    的数,那就是内存溢出。 2. 内存泄漏 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的
    的头像 发表于 11-10 11:04 375次阅读
    <b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>如何避免

    什么是内存泄漏?如何避免JavaScript内存泄漏

    JavaScript 代码中常见的内存泄漏的常见来源: 研究内存泄漏问题就相当于寻找符合垃圾回收机制的编程方式,有效避免对象引用的问题。
    发表于 10-27 11:30 124次阅读
    什么是<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>?如何避免JavaScript<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>

    Nginx常用配置与命令

    对于前端来说 Node.js 并不陌生, Nginx 和 Node.js 的很多理念类似, HTTP 服务器、事件驱动、异步非阻塞等,且 Nginx 的大部分功能使用 Node.js 也可以实现,但 Nginx 和
    发表于 10-18 10:04 159次阅读
    Nginx常用配置与命令

    不同语言运行100万个并发任务需要多少内存

    在这篇博客文章中,我深入探讨了异步和多线程编程在内存消耗方面的比较,跨足了如Rust、Go、Java、C#、Python、Node.js 和 Elixir等流行语言。
    的头像 发表于 07-12 14:14 352次阅读
    不同语言运行100万个并发任务需要多少<b class='flag-5'>内存</b>?

    Linux内存泄漏检测实现原理与实现

    在使用没有垃圾回收的语言时(如 C/C++),可能由于忘记释放内存而导致内存被耗尽,这叫 内存泄漏
    的头像 发表于 07-03 09:21 423次阅读
    Linux<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>检测实现原理与实现

    英特尔Edison Node JS物联网报警系统

    电子发烧友网站提供《英特尔Edison Node JS物联网报警系统.zip》资料免费下载
    发表于 06-13 17:08 0次下载
    英特尔Edison <b class='flag-5'>Node</b> <b class='flag-5'>JS</b>物联网报警系统

    怎么解决C语言中的内存泄漏问题呢?

    只有在堆内存里面才会发生内存泄漏的问题,在栈内存中不会发生内存泄漏。因为栈
    发表于 06-11 17:31 390次阅读
    怎么解决C语言中的<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>问题呢?

    node.js实战源码

    node.js实战源码
    发表于 05-16 18:06 0次下载

    MQTT客户端内存泄漏问题如何解决?

    嗨, 我是 nodeMCU 的新手,所以这可能是我 我在 NodeMCU 0.9.6 build 20150704 的 MQTT 客户端中看到内存泄漏 这 是我的代码,下面 所以它连接并断开与由触发
    发表于 05-09 06:10