myfreax

了解Node.js应用中的内存泄漏

虽然Node.js适用于许多应用程序,但由于其可伸缩性,它对堆大小有一些限制。 为了提高Node.js应用程序的效率,重要的是要理解为什么内存发生泄漏,更重要的是如何调试它

11 min read
By myfreax
了解Node.js应用中的内存泄漏

在本文中,我们将学习内存泄漏是什么,原因导致它们,以及它们在Node.js应用程序中的含义。

虽然Node.js适用于许多应用程序,但由于其可伸缩性,它对堆大小有一些限制。 为了提高Node.js应用程序的效率,重要的是要理解为什么内存发生泄漏,更重要的是如何调试它。

了解内存管理可降低浪费应用程序资源的可能性,但内存泄漏的难以查找的性质和它们可以对性能的意外效果使得了解Node.js管理内存的内存至关重要。

node.js如何管理内存?

性能对于采用和使用应用程序至关重要,这使得内存管理成为软件开发的重要方面。 出于这个原因,node.js具有与对象生命周期相关的内置内存管理机制。

例如,Node.js动态地将内存分配给对象,并将这些未使用对象释其放空间。 一旦内存被释放,它可以重复使用。

Node.js中的内存分配和释放主要由垃圾收集器(GC)处理。 垃圾收集是指查找所有实时值和返回系统使用的内存的过程,以便稍后再循环。

Node.js GC使用堆数据结构存储对象的引用。 堆具有有限大小,GC计算资源耗尽的速度,以指示是否有可能存在内存泄漏。

内存分配都会让您更接近垃圾收集器暂停。 GC通过识别不再使用内存或无法访问的对象,然后重新分配或将内存释放到OS。

在较低级别中,Node.js使用V8 JavaScript引擎。“V8是谷歌的开源,高性能JavaScript和Webassembly 引擎,使用C ++编写。” V8执行代码并管理其运行时执行所需的内存。

根据程序的要求分配和释放。 虽然Node.js GC在管理内存时做得很好,但由于各种原因仍然发生泄漏。

为什么内存泄漏发生?

“内存泄漏是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费”(维基百科)。

当短寿的对象附加到预期长寿命的对象时会发生内存泄漏。 在此代码片段中显示了内存如何泄漏的真实例子:

const requests = new Map();
app.get( "/", (req,res) => {
    requests.set(req.id, req);
    res.status(200).send("Hello World");
});

上面的示例可能会导致内存泄漏,因为持有req对象的Map新实例requests变量是全局的。 因此,每次请求服务器时,都会对requests对象进行内存分配。requests对象实例在全局上。因此得不到释放

新实例的保证内存分配意味着对象将永远存活。 当请求的数量消耗超出应用程序的资源时,应用程序最终将耗尽内存并崩溃。

如果它们没有注意到,尤其是在生产环境中,内存泄漏可能是有问题的。 当应用程序CPU和内存使用量不可预期的增加时,很大可能是内存泄漏。

您可能会注意这一点:内存使用量增长到应用程序没有响应的程度时。 即内存已满,会发生服务器故障这种情况。

当发生这种情况时,我们大多数人都倾向于重新启动应用程序,并解决了所有性能问题。 但是,这种临时解决方案不会摆脱错误,这可能会触发意外的副作用,尤其是当服务器处于沉重负载时。

调试内存泄漏

在许多情况下,没有明确的了解为什么发生了内存泄漏。 事实上,这种观察可能会在他们的发生时刻被忽视,特别是在开发期间。

一个调试策略是看对象生命周期中的必要条件。 尽管程序的性能可能是稳定或看似最佳的,但它有可能触发内存泄漏可能。

不是垃圾收集应该解决这个问题吗?

在内存管理的上下文中,垃圾是指内存中无法达到的值,并且随着前面提到的,垃圾收集是指识别实时值并将死亡值返回到系统所使用的内存的过程。

这意味着垃圾收集器通过跟踪来自某些“root”对象的引用链来追踪哪些对象并确定应该释放哪些对象; 其余的被认为是垃圾。 垃圾收集的主要目的是减少程序中的内存泄漏。

但垃圾收集并没有完全解决内存泄漏,因为垃圾收集只收集它所知不使用的东西。 从“root”对象能到达的对象都不被视为垃圾。

GC是处理内存泄漏的最方便的方法,其中一个缺点是收集的过程中消耗额外的资源。 影响应用程序的性能。

管理内存泄漏

内存泄漏不仅难以捉摸,而且难以识别和调试,尤其是在使用API时。 在本节中,我们将学习如何使用可用的工具捕获内存泄漏。

我们也将讨论用于在生产环境中调试泄漏的合适方法 - 不会破坏代码的方法。 您在开发中捕获的内存泄漏更容易调试。

如果您怀疑应用程序中的内存泄漏,它可能是应用程序的RSS太小,未提高增加的结果, 对于应用程序来处理工作负载RSS需要合适的值,RSS变得太高,这可能导致它崩溃而不会出现“内存”警告。

帮助调试内存泄漏的工具

node-heapdump

heapdump 模块适用于后验证调试。 它会在SIGUSR2上生成heap堆转储。 帮助你在开发环境中轻松捕获错误,请将HeapDump添加为对项目的依赖性:

npm install heapdump --save

然后将其添加到您的根文件中:

var heapdump = require("heapdump");

You are now set to use heapdump to take some heap snapshots. You can call the function:

您现在已设置为使用HeaPdump获取堆快照。 您可以调用它的功能:

heapdump.writeSnapshot(function(err, filename){
console.log("Sample dump written to", filename);
});

一旦您有写入的快照,您可以比较它们并查找应用程序中导致内存泄漏的内容。

node-inspector

这是使用Chromium Blink开发工具的调试器界面来调试Node.js

process.memoryUsage

这是监视Node.js应用程序中内存使用的简单方法。

方法会返回:

  • rss  -RSS大小是指该进程主存储器中占用的空间量,包括代码段,堆和堆栈。 如果您的RSS正在上升,则可能需要您调试应用程序的泄漏内存
  • heapTotal  -  JavaScript对象可用的内存量
  • heapUsed  -  JavaScript对象占用的总量
  • external  -  node.js使用的堆数据(缓冲区)消耗的内存量。 这是存储对象,字符串和闭包的地方

例如,此代码:

console.log(process.memoryUsage());
console.log(process.memoryUsage());

将返回这样的东西:

{
  rss: 4935680
  heapTotal:1826816
  heapUsed:650472
  External: 49879
}

这向您展示了应用程序消耗多少内存。 在生产环境中,这不是一种使用的方法,因为它打开了浏览器页面并向您展示了数据。

Chrome DevTools

Chrome DevTools可以真正有助于捕获和调试内存泄漏。 要打开DEV工具,请打开Chrome,单击汉堡包图标,选择更多工具,然后单击开发人员工具

Chrome提供一系列工具来帮助调试内存和性能问题。

这些工具包括分配时间表,采样堆分析器和堆快照。

要设置Chrome DevTools来调试 Node.js应用程序,您需要:

  • 最新版本的node.js
  • 您首选的代码编辑器
  • Chrome Web浏览器

在终端上打开node.js项目,然后键入node --inspect

Running node --inspect On The Terminal

在您的浏览器中,键入about:inspect。 这应该打开一个像下面一样的窗口:

Running about:inspect In Chrome

最后,单击Open dedicated DevTools for Node开始调试代码。

Chrome's Dedicated Dev Tools For Node

我们将通过在下一节中说明堆快照使用。

堆快照

堆快照是调试生产环境中泄漏的有效方法。 它们允许开发人员录制堆并以后用Chrome DevTools分析器分析它们。

注意。拍摄堆快照可能是昂贵的,因为我们必须在每个快照后做一个完整的垃圾收集。

A Heap Snapshot Of Our Node App

优化内存泄漏

我们都关心性能并保持快速,确保我们只使用必要的最小内存量。 内存分析可能很有趣,但有时,感觉就像一个黑匣子。

很难避免内存泄漏,因为您无法真正了解您的对象将在如何随时间使用。 但是,有些方法可以在Node.js应用程序中减轻内存泄漏。 以下是开发人员在编写应用程序时陷入普通陷阱。

持续关注对DOM元素的引用

DOM对象引用的JavaScript对象非常多,直到DOM对象链接回这样的JavaScript对象,形成引用周期。 这会成为问题,特别是在长期运行的应用中,因为内存未从循环对象释放,从而导致内存泄漏。 为了确保事件处理程序没有直接引DOM元素,您应该通过数组间接处理事件。

避免循环引用对象.

循环引用意味着对象调用自身, 这将对象界定永远活跃,最终可能导致内存泄漏。

以下是对象引用本身的的示例:

var obj = {}
obj.a = a;

var obj是一个空对象,a是回到同一对象的属性。

这种类型的行为会导致内存泄漏是不可能修复的。 最好的方法是只要摆脱对象引用。

unbind事件侦听器

绑定太多元素会让应用较慢。 例如Andrew Markham-Davies Jsperf 上创建的测试用例。 第一个事件绑定到许多元素,尽管它是在每种情况下绑定的一个事件,但函数速度较慢。

在第二场景中,该事件绑定到父元素,并为要监听元素分配ID。 这使得它更有效,因为它针对具有ID而不是类的元素,从而使其更快。

管理本地缓存

缓存存储数据以便在稍后需要时更快更容易地检索。 缓存可以是提高性能的好方法。 内存缓存模块可以是Node.js应用程序中内存中高速缓存的好工具。 您只需下载依赖项并在Node.js应用程序中使用它。

结论

很难避免内存泄漏,因为某些程序在运行时增加了它们的内存占用空间。 关键是要了解预期的对象的生命周期,并学习如何使用可有效减轻内存泄漏的工具。