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

    文章

    2962

    浏览量

    73796
  • NODE.JS
    +关注

    关注

    1

    文章

    46

    浏览量

    32743
收藏 人收藏

    评论

    相关推荐

    工程师必备!Node.js和常见管理工具介绍(附操作演示)

    语言及其相关生态中的一些基础而关键的概念,以及它们是如何在互联网历史浪潮中产生的。一JavaScript与Node.js1JavaScriptJavaScript,即JS
    的头像 发表于 08-30 12:34 219次阅读
    工程师必备!<b class='flag-5'>Node.js</b>和常见管理工具介绍(附操作演示)

    ARMxy ARM 物联网边缘计算网关支持 Node-RED 用于工业控制

    在恶劣环境下稳定运行。丰富的通信接口(如网口、USB、RS485 等)及 WiFi/4G 支持,使网关能够灵活适应不同网络环境。 Node-RED 是一个基于 Node.js 的流程编排工具,通过
    的头像 发表于 08-19 17:01 250次阅读
    ARMxy ARM 物联网边缘计算网关支持 <b class='flag-5'>Node</b>-RED 用于工业控制

    如何检测内存泄漏

    检测内存泄漏是软件开发过程中一项至关重要的任务,它有助于识别和解决那些导致程序占用过多内存资源,从而影响程序性能甚至导致程序崩溃的问题。以下将详细阐述几种常见的内存
    的头像 发表于 07-30 11:50 1242次阅读

    NONOS 1.5.3/1.5.4 SSL内存泄漏的原因?

    我已经通过随附的代码验证了当发生 SSL 握手错误时,会生成内存泄漏 此外,espconn_reconnect_callback不称为信令ESPCONN_HANDSHAKE - TCP SSL 握手
    发表于 07-18 07:24

    鸿蒙开发实战:网络请求库【axios】

    [Axios] ,是一个基于 promise 的网络请求库,可以运行 node.js 和浏览器中。本库基于[Axios]原库v1.3.4版本进行适配,使其可以运行在 OpenHarmony,并沿用其现有用法和特性。
    的头像 发表于 03-25 16:47 3574次阅读
    鸿蒙开发实战:网络请求库【axios】

    C语言内存泄漏问题原理

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

    【鸿蒙】webview内存泄漏问题的分析报告

    1 关键字 webview;内存泄漏 2 问题描述 问题现象:在 3.1release 版本和 3.2bete1 版本中,在 RK3568 上使用 etsWeb 和其他浏览器时,webview 所占
    的头像 发表于 03-02 15:12 2028次阅读

    鸿蒙 Harmony 工具篇

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

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

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

    javascript运行环境有哪些

    使用 SpiderMonkey 引擎,而 Safari 使用 JavaScriptCore 引擎等。 Node.jsNode.js 是基于 Chrome V8 引擎的 JavaScript 运行环境。它允许在服务
    的头像 发表于 11-27 16:11 2438次阅读

    Nodejs的CommonJS规范实现原理

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

    如何发现内存泄漏

    检测两个角度介绍在 Linux 环境进行内存泄漏检测的方法,并重点介绍静态分析工具 BEAM、动态监测工具 Valgrind 和 rational purify 的使用方法。相信通过本文的介绍,能给大家对处理其它产品或项目内存
    的头像 发表于 11-13 15:41 550次阅读

    线程内存泄漏问题的定位

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

    内存泄漏会产生哪些后果

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

    内存泄漏如何避免

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