内存泄漏可能是 Node.js 中的一个严重问题,可能会影响 Node 应用程序的性能。尽管看起来后端的困境导致应用程序失败,但错误的真正来源可能是 Node.js 内存泄漏。了解内存泄漏是什么、它们为什么会发生以及如何检测和解决 Node.js 中的内存泄漏非常重要,以便最终找到修复内存泄漏的根源。
什么是内存泄漏?
当应用程序占用了计算机的部分内存并且未能在适当的时候释放它时,就会发生内存泄漏。随着时间的推移,这会导致计算机内存不足,从而导致性能下降。当发生 Node.js 内存泄漏时,受影响的应用程序可能运行缓慢或完全冻结。内存泄漏会导致严重的性能问题,需要对其进行诊断和解决。
Node.js 如何管理内存?
V8 垃圾收集器(Google 的 JavaScript 引擎)的复杂化可能会导致 Node.js 内存泄漏。 V8 是一个运行 Node.js 应用程序的开源引擎,尽管它不是专门为 Node 设计的。 V8 代表 Node.js 应用程序处理内存管理,使 Node 开发人员的工作更轻松,因为它消除了编写处理内存管理代码的手动任务。然而,自动内存管理也可能是一个问题,因为它夺走了应用程序开发人员的控制权。
当 V8 需要为 Node.js 应用程序中的对象分配内存时,它通常通过使用指针将对象或变量分配给新的一块内存来实现。最终,可用内存开始用完,此时 V8 开始垃圾收集过程以释放内存。
V8 垃圾收集器必须确定哪些内存区域可以在不影响应用程序性能的情况下重新分配。它遵循指针来找出哪些对象当前是活动的,哪些对象不能被现有的指针集访问。包含无法访问对象的内存称为“死”区域,可以安全地重新分配。
什么导致内存泄漏?
为了避免内存泄漏,V8 需要知道所有对象在内存中的确切位置。如果对象被错误地识别为指针,则会导致内存泄漏。当对象实际使用的内存超出分配给该对象的内存时,就会发生内存泄漏。
内存泄漏的另一个潜在原因是回调函数,这是一个可以作为参数传递给其他代码的 JavaScript 函数。 V8 垃圾收集器并不总是知道删除可多次调用的回调函数中使用的对象是否安全,导致内存在应该重新分配的时候没有被重新分配。回调函数是闭包的例子,闭包函数可以访问调用它的函数的变量。闭包是内存问题的一个众所周知的原因,因为只要闭包函数需要使用它们,变量就必须保留在内存中。
Node.js 内存泄漏最常发生在包含大量数据的对象中,例如数组或哈希图。但是,Node.js 内存泄漏也可能发生在其他对象类型中,例如字符串。
有时,Node.js 应用程序内存泄漏的原因不在于应用程序的代码,而在于应用程序执行所依赖的上游代码。这使得 Node.js 内存泄漏难以诊断。如果您多年来一直盯着 JavaScript 代码,并且确信它不会导致内存泄漏,那么请考虑查看上游代码以查找问题的根源。
总之,您应该考虑以下可能导致 Node.js 内存泄漏的原因:
- 回调和关闭
- 您定义的类的泄漏构造函数
- 集合对象,例如数组和哈希图
- 上游代码
Node.js 应用程序内存泄漏的症状是什么?
内存泄漏会影响 Node.js 应用程序的性能,导致它们运行缓慢、出现故障或完全死机。许多泄漏开始时非常缓慢,但随着时间的推移,它们会导致应用程序变慢,因为 V8 需要在垃圾收集模式下花费越来越多的时间来管理应用程序的内存使用。
内存泄漏也会导致 Node.js 应用程序出现奇怪的行为。例如,您可能会注意到您的应用程序无法打开新的数据库连接。发生这种情况是因为泄漏代码挂在对应用程序打开新连接所需的资源的引用上。您可能会发现需要重新启动应用程序才能恢复其性能。
并非所有内存泄漏都会在开发或测试阶段引起明显的症状。但是,仍然值得尝试在您的应用程序中查找内存泄漏,以便您可以消除它们。随着您的应用程序越来越受欢迎,对其提出要求的用户数量也会越来越多。即使是相对缓慢的内存泄漏也会损害应用程序的性能,使其变慢并阻止它为您的用户提供最佳性能。如果您任由内存泄漏继续发生,您的应用程序最终会崩溃,这可能会非常尴尬,并且会损害您在客户和整个在线社区中的声誉。
每次内存使用量开始失控时,为您的应用程序提供更多 RAM 或重新启动您的服务可以防止危机。然而,最好是追踪问题并修复它,这样你的应用程序才能高效且一致地运行,而不需要你持续监控它或担心它崩溃。
如何诊断 Node.js 应用程序中的内存泄漏?
许多指标可以诊断 Node.js 应用程序中的内存泄漏。这些包括堆使用、堆增长和垃圾收集频率。您还可以使用工具来帮助您检测 Node.js 应用程序中的泄漏,其中一些已在 Mozilla 的摘要中列出。
堆分析器是一个非常有用的工具,用于诊断 Node 应用程序中的内存泄漏。非泄漏内存配置文件显示内存使用量随着传入请求的增加而增加。处理请求后,您应该看到内存使用量减少,因为不再需要的对象被销毁,并且已使用的内存被释放。
泄漏内存配置文件看起来与正常配置文件非常不同。内存使用量会随着时间的推移而增加。应用程序打开的时间越长,可用内存就越少,直到您必须重新启动应用程序才能将内存使用率降低到正常水平并恢复应用程序的性能。请注意,内存泄漏可能很快,在这种情况下它们很快并且很容易在堆内存使用中注意到,或者很慢,在这种情况下内存使用量逐渐增加。
查看各种类型对象的实例计数有助于确定 Node.js 应用程序中内存泄漏的来源。如果特定类型对象的实例计数增长强劲并且没有回落,那么这种类型的对象很可能是内存泄漏的根源。通常,您会希望每个对象的实例计数在每个垃圾收集周期后回落,因此寻找没有这种行为的对象。
分析这些指标可以帮助您找出代码的哪一部分导致了问题,这样您就可以追踪错误并尽快修复它。例如,如果您编写了一个名为 myClass 的泄漏类,那么您可能会看到类型为 myClass 的对象用完了堆堆栈中的大部分内存。然后您可以检查 myClass 类的定义以查找允许发生内存泄漏的代码。
另外,请注意 V8 引擎运行垃圾回收的频率。发生这种情况时,您可能会注意到您的应用程序变慢了。您的代码越泄漏,V8 需要执行垃圾收集的频率就越高。
处理 Node.js 内存泄漏
在开发 Node 应用程序时诊断和控制内存泄漏非常重要。请注意,您的应用程序可能会遇到来自多个来源的内存泄漏,因此当您认为已消除内存泄漏时,请重复测试以确保泄漏已消失。
尽管诊断和消除内存泄漏需要一些时间,但在开发应用程序时努力消除泄漏是值得的。如果您稍后尝试构建有漏洞的代码以向您的应用程序添加更多功能,您可能会发现它的性能对应用程序的影响太大以至于无法对您的用户有用。
通过学习处理 Node.js 内存泄漏,您可以训练自己构建运行良好的更好应用程序。您将学习在编写新代码时考虑内存泄漏的可能性。例如,当创建一个新类时,您需要注意创建大对象的构造函数,这些对象在应该删除的时候没有被删除。您可以通过在编写代码时始终考虑如何以及何时创建和销毁对象来帮助防止 Node.js 内存泄漏。