Node.js에서 메모리 누수를 확인하는 방법은 무엇입니까? 다음 글에서는 Nodejs 힙 할당에 대해 소개하고, 힙 할당을 최소화하고 메모리 누수를 방지하는 방법을 소개하겠습니다. 도움이 되셨으면 좋겠습니다!
메모리 관리 문제는 컴퓨터 분야에서 항상 많은 관심을 받아왔습니다. 컴퓨터에서 실행되는 각 소프트웨어에는 컴퓨터의 제한된 메모리 중 작은 부분이 할당됩니다. 이 메모리는 주의 깊게 관리되어야 하며 적절한 시기에 할당되거나 해제되어야 합니다.
Nodejs
는 효율적인 자동 가비지 수집 메커니즘을 통해 지루한 메모리 관리 작업을 처리할 수 있으므로 개발자가 다른 작업에 참여할 수 있습니다. Nodejs
는 개발자가 메모리 관리 문제를 해결하는 데 도움을 주었지만, 대규모 애플리케이션을 개발하는 과정에서는 개발자가 V8
과 Nodejs의 메모리 관리 메커니즘은 여전히 매우 중요합니다. <code>Nodejs
可以通过其高效的自动垃圾回收机制,来处理内存管理的繁琐任务,从而将开发人员解放出来,从事其他任务。虽然说 Nodejs
已经帮助开发者解决了内存管理的问题,但是在面对大型应用开发的过程中,对于开发者理解 V8
和 Nodejs
中的内存管理机制仍然非常重要。
这片文章主要介绍了如何在堆中分配和释放内存,并且帮助你知道如何最小化堆分配和防止内存泄漏。【相关教程推荐:nodejs视频教程、编程教学】
Nodejs
中的堆分配JavaScript
和 Node.js
为你抽象了很多东西,并且在后台完成了大部分繁重的工作。
我们知道,当一段代码被执行的时候,代码中的变量和对象会被存储在栈内存或者堆内存中,JavaScript
代码会被存储在将要被执行的执行上下文中。
ECMAScript
规范本身并没有规定如何分配和管理内存。这是一个依赖于 JavaScript
引擎和底层系统架构的实现细节。深入理解引擎是如何处理变量的已经超出了本文的范围,但如果你想了解更多关于V8
是如何做到这一点的,请参考文章JavaScript内存模型揭秘
和数据是如何存储在V8 JS引擎内存中的?
。
Node.js
中高效的堆内存使用很重要?存储在堆中的内存变量将一直存在,除非它被垃圾收集器删除或释放。堆内存是一大块连续的内存块,即使再被分配和释放之后,仍然会保持这种状态。
不幸的是,由于堆内存收集和释放方式,内存可能会被浪费,从而导致泄漏。
V8
使用的是分代垃圾收集机制,即它将对象划分为不同的代(新生代和老生代)。代空间又会被划分为不同的区——例如新生代由新空间组成,老生代会被划分为旧空间、映射空间和大对象空间。新对象最初被分配到新生代空间中,当新生代空间使用完时,垃圾收集器将执行清理机制以释放空间。在一次 GC
运行中幸存下来的对象会被复制到新生代的中间中间中,在第二轮运行中幸存下来的对象会被移动到老生代中。
由于运行程序先进行内存收集,占用了宝贵的虚拟内存资源,因此当不再需要内存时,程序必须释放内存,这就是内存释放。
此外,如果内存被释放了(不管先前它在堆中的哪个位置释放),堆内存将被合并为一个连续的内存块形式。由于堆内存复杂性的增加,在这里存储会导致更高的性能开销(但使得后续的存储有了更大的灵活性)。
虽然 Nodejs
拥有高效的垃圾回收机制,但是堆内存的低效使用可能导致内存泄漏。应用程序可能会占用太多的内存,甚至崩溃。
垃圾回收器会寻找并释放孤立的内存空间,但有时它可能无法跟踪每一块内存。这可能导致不必要的负载增加,特别是对于大型应用程序。稍后我们将详细讨论 Nodejs
Nodejs
JavaScript
및 Node.js
의 힙 할당은 많은 작업을 추상화하고 대부분의 작업을 백그라운드에서 수행합니다. 힘든 일. 🎜🎜우리는 코드 조각이 실행될 때 코드의 변수와 객체가 스택 메모리나 힙 메모리에 저장되고 JavaScript
코드가 실행 컨텍스트에 저장되어 실행. 🎜🎜ECMAScript
사양 자체에서는 메모리 할당 및 관리 방법을 지정하지 않습니다. 이는 JavaScript
엔진과 기본 시스템 아키텍처에 따라 달라지는 구현 세부정보입니다. 엔진이 변수를 처리하는 방법에 대한 깊은 이해는 이 문서의 범위를 벗어납니다. 그러나 V8
이 이를 수행하는 방법에 대해 자세히 알아보려면 JavaScript 메모리 모델 공개
및 V8 JS 엔진 메모리에 데이터가 저장되는 방식 ?
. 🎜Node.js
에서 효율적인 힙 메모리 사용이 왜 중요한가요?V8
은 객체를 여러 세대(젊은 세대와 오래된 세대)로 나누는 세대별 가비지 수집 메커니즘을 사용합니다. 세대 공간은 서로 다른 영역으로 구분됩니다. 예를 들어 신세대는 새로운 공간으로 구성되고, 구세대는 구 공간, 매핑 공간, 대형 개체 공간으로 구분됩니다. 새로운 개체는 처음에 새로운 세대 공간에 할당됩니다. 새로운 세대 공간이 모두 사용되면 가비지 수집기는 공간을 확보하기 위해 정리 메커니즘을 수행합니다. 한 번의 GC
실행에서 살아남은 개체는 Young Generation의 중간에 복사되고, 두 번째 실행에서 살아남은 개체는 Old Generation으로 이동됩니다. 🎜🎜실행 중인 프로그램은 먼저 메모리를 수집하여 귀중한 가상 메모리 자원을 점유하므로 메모리가 더 이상 필요하지 않을 때 프로그램은 메모리를 해제해야 합니다. 이것이 바로 메모리 해제입니다. 🎜🎜또한 메모리가 해제되면(이전에 힙에서 해제된 위치에 관계없이) 힙 메모리는 연속적인 메모리 블록으로 병합됩니다. 힙 메모리의 복잡성 증가로 인해 여기에 저장하면 성능 오버헤드가 높아집니다(그러나 후속 저장에서는 더 큰 유연성이 허용됩니다). 🎜🎜Nodejs
에는 효율적인 가비지 수집 메커니즘이 있지만 힙 메모리를 비효율적으로 사용하면 메모리 누수가 발생할 수 있습니다. 응용 프로그램은 너무 많은 메모리를 차지하거나 심지어 충돌할 수도 있습니다. 🎜Nodejs
에서 가비지 수집기가 어떻게 작동하는지 자세히 설명하겠습니다. 🎜🎜메모리 누수의 가장 일반적인 원인은 다음과 같습니다. 🎜객체에 대한 참조를 유지하기 위해 여러 변수 포인터를 사용하는 것은 매우 일반적인 작업입니다. 이는 매우 편리하지만 개체에 대한 참조 중 하나가 가비지 수집기에 의해 수집되고 다른 참조는 그렇지 않은 경우 메모리 누수가 발생할 수도 있습니다.
Node.js
및 JavaScript
애플리케이션에서 정리하는 것을 잊은 타이머 및 콜백 함수도 메모리 누수의 두 가지 일반적인 원인입니다. 타이머에 바인딩된 개체는 시간이 초과될 때까지 가비지 수집되지 않습니다. 타이머가 계속 실행되면 참조된 개체는 가비지 수집기에 의해 수집되지 않습니다. 이는 개체를 참조하는 변수 포인터가 없는 경우에도 발생하므로 힙에서 메모리 누수가 발생합니다. Node.js
和 JavaScript
应用程序中,被忘记清理的计时器和回调函数也是导致内存泄漏的两个常见原因。被绑定到计时器的对象直到超时才会被垃圾收集。如果计时器一直运行,则被引用的对象将永远不会被垃圾回收器收集。即使没有变量指针引用对象,也会发生这种情况,因此将在堆中造成内存泄漏。
思考下示例代码:
const language = () => { console.log("Javascript");】 // 递归自身 setTimeout(() => language(), 1000); }
上面这段代码将会被一直运行,并且永远不会被垃圾回收器回收
Nodejs
中的内存泄漏这有几个工具可以用于检测和调试 Nodejs
中的内存泄漏,包括 Chrome DevTools
,Node
的进程。memoryUsage API
和 AppSignal
的垃圾收集器看板。
Chrome DevTools
可能是最简单的工具之一。要启动调试器,需要以 inspect
模式启动 Node
。运行node --inspect
来执行此操作。
更具体地说,如果你的 Node
的入口是 app.js
,你需要运行 node --inspect app.js
来调试Node 应用程序。然后,打开 Chromium
浏览器,进入 chrome://inspect
。你也可以在 Edge://inspect 打开检查器页面。在检查器页面,你应该看到这样一个页面:
注意,你正在尝试调试的 Node
应用程序出现在检查器页面的底部。单击 inspect
打开调试器。调试器有两个重要的选项卡—— Memory
和 Profiler
——但在本讨论中,我们将重点关注 Memory
选项卡。
使用 Chrome
调试器查找内存泄漏最简单的方法是使用堆快照
。快照可以帮助你检查一些变量或检查它们的保留区大小。
你也可以通过比较多张快照发现内存泄漏。对于一个实力来说,你可以在内存泄漏之前和之后分别保存一张快照,然后比较两者。为了获取快照,你可以通过在 Heap snapshot
上点击一下,然后点击 *Take snapshot
按钮。这可能需要一些时间,这取决于应用程序的 Total JS
堆大小。你也可以通过点击 DevTool
底部的 load
按钮来加载现有的快照。
当你有了两张或者多张快照时,你就可以非常容易的比较堆分配,已找到内存泄漏的原因。你可以通过以下方式查看快照:
Summary
:根据构造函数名称对 Node
应用程序中的对象进行分组展示
Comparison
: 显示两张快照之间的区别
Containment
:允许你查看堆内并分析全局名称空间中引用的对象
Statistics
:
在 DevTools
堆分析器中有两列很突出——即 Shallow Size
和 Retained Size
。
Shallow Size
表示的是对象自身在内存中的大小。这个内存大小对于大多数对象来说并不大,但数组和字符串类型除外。另一方面, Retained Size
是党有问题的对象和依赖对象被释放或从根节点无法访问时释放的内存大小。
Chrome DevTools
并不是获取堆快照的唯一方法。如果你使用的是 nodejs
12.0 或更高版本,你还可以通过运行 node --heapsnapshot-signal
命令:
node --heapsnapshot-signal=SIGUSR2 app.js
虽然可以使用任何标志,但建议使用用户定义的信号SIGUSR1
或SIGUSR2
。
如果你从正在服务端运行的应用中获取一张对快照,则可以使用 V8
包中的 writeHeapSnapshot
require("v8").writeHeapSnapshot();🎜위 코드는 항상 실행되며 가비지 수집기에 의해 수집되지 않습니다.🎜
메모리 누수를 찾는 방법 Nodejs
Chrome DevTools
코드를 포함하여 Nodejs
에서 메모리 누수를 감지하고 디버그하는 데 사용할 수 있는 여러 도구가 있습니다. >, 노드
의 프로세스입니다. memoryUsage API
및 AppSignal
용 가비지 수집기 대시보드입니다. 🎜Chrome DevTools
는 아마도 가장 쉬운 도구 중 하나일 것입니다. 디버거를 시작하려면 inspect
모드에서 Node
를 시작하세요. 이를 수행하려면 node --inspect
를 실행하세요. 🎜🎜더 구체적으로, Node
항목이 app.js
인 경우 node --inspect app.js
를 실행하여 디버깅해야 합니다. 노드 애플리케이션. 그런 다음 Chromium
브라우저를 열고 chrome://inspect
를 입력하세요. Edge://inspect에서 검사기 페이지를 열 수도 있습니다. 검사기 페이지에 다음과 같은 페이지가 표시됩니다: 🎜🎜Node
애플리케이션이 검사기 페이지 하단에 표시됩니다. 검사
를 클릭하여 디버거를 엽니다. 디버거에는 메모리
와 프로파일러
라는 두 가지 중요한 탭이 있습니다. 하지만 이 토론에서는 메모리
탭에 중점을 둘 것입니다. 🎜🎜🎜🎜 사용 Chrome
디버거에서 메모리 누수를 찾는 가장 쉬운 방법은 힙 스냅샷
을 사용하는 것입니다. 스냅샷은 일부 변수를 확인하거나 예약 크기를 확인하는 데 도움이 될 수 있습니다. 🎜🎜여러 스냅샷을 비교하여 메모리 누수를 찾을 수도 있습니다. 좋은 아이디어를 얻으려면 메모리 누수 전후의 스냅샷을 저장하고 둘을 비교할 수 있습니다. 스냅샷을 찍으려면 힙 스냅샷
을 한 번 클릭한 다음 *스냅샷 찍기
를 클릭하면 됩니다. 단추. 애플리케이션의 총 JS
힙 크기에 따라 다소 시간이 걸릴 수 있습니다. DevTool
하단에 있는 load
버튼을 클릭하여 기존 스냅샷을 로드할 수도 있습니다. 🎜🎜두 개 이상의 스냅샷이 있는 경우 힙 할당을 쉽게 비교하고 메모리 누수의 원인을 찾을 수 있습니다. 다음과 같은 방법으로 스냅샷을 볼 수 있습니다. 🎜🎜🎜🎜요약
: 생성자 이름을 기준으로 노드
애플리케이션의 개체를 그룹화합니다🎜 🎜 🎜🎜비교
: 두 스냅샷의 차이점을 보여줍니다. 🎜🎜🎜🎜격납
: 다음에서 볼 수 있습니다. 전역 네임스페이스 🎜🎜🎜🎜통계
에서 참조되는 개체를 힙하고 분석합니다. 🎜🎜🎜🎜🎜🎜DevTools
힙 분석기에서 눈에 띄는 두 개의 열이 있습니다. 즉 얕은 크기
및 보유된 크기
. 🎜🎜Shallow Size
는 메모리에 있는 개체 자체의 크기를 나타냅니다. 이 메모리 크기는 배열 및 문자열 유형을 제외한 대부분의 개체에서 크지 않습니다. 반면, 보유된 크기
는 문제의 개체와 종속 개체가 해제되거나 루트 노드에서 액세스할 수 없게 될 때 해제되는 메모리 크기입니다. 🎜🎜Chrome DevTools
가 힙 스냅샷을 얻는 유일한 방법은 아닙니다. nodejs
12.0 이상을 사용하는 경우 node --heapsnapshot-signal
명령을 실행할 수도 있습니다. 🎜let data = {}; data.el = data; let obj1 = {}; let obj2 = {}; obj1.a = obj2; obj2.a = obj1;🎜 어떤 플래그든 사용할 수 있지만 사용하는 것이 좋습니다. 사용자 정의 신호
SIGUSR1
또는 SIGUSR2
. 🎜🎜서버에서 실행 중인 애플리케이션에서 쌍 스냅샷을 찍는 경우 V8
패키지의 writeHeapSnapshot
기능을 사용할 수 있습니다. 🎜require("v8").writeHeapSnapshot();
这个方法要求 Nodejs
的版本高于 11.13。在早期的版本中,你可以使用相关的包来实现。
使用 Chrome DevTools
获取堆快照并不是调试内存问题的唯一方法。你也可以使用Allocation instrumentation on timeline
跟踪每个堆分配的情况。
内存分配时间轴显示了随时间变化的测量内存分配的情况。要启用此功能,需要先启动分析器(Profiler
),然后运行应用程序示例以开始调试内存问题。如果你希望记录长时间运行的内存分配操作,并想要更小的性能开销,那么最好的选择是分配抽样方法。
Node
的 process.memoryUsage
API你也可以使用 Node
的 process.memoryUsage
API来观察内存使用情况。运行 process.memoryUsage
,你可以访问以下内容:
rss
:已分配的内存量heapTotal
:已分配堆的总大小heapUsed
:当执行进程时被使用内存总量arrayBuffers
:为 Buffer 实例分配的内存大小AppSignal
的垃圾收集器看板为了可视化堆的变化情况,AppSignal
提供了一个方便的垃圾收集看板。当你将 Node.js
应用连接到AppSignal
时,这个看板会自动为你生成!
看看这个例子,在“V8 Heap Statistics
”图表中,你可以清楚地看到内存使用的峰值:
如果看板中中的数据出现一个稳定增长的趋势,这意味着你的代码中或者依赖中存在内存泄漏的情况。
如果你知道如何发现内存泄漏,但如何修复它们?我们可能很快就知道。但是首先重要的是理解 Nodejs
和 V8
是如何进行垃圾收集的。
垃圾回收机制会在不需要的时候释放内存。为了更高效的工作,垃圾回收算法必须正确的定义和识别不需要再内存中继续存储的内容。
在引用计数 GC
算法中,如果堆中的对象在堆栈中不再有引用,则该对象将被垃圾收集。该算法通过计数引用来工作——因此,如果引用计数为零,则对象将进行垃圾收集。尽管这个算法大多数时候都有效,但它在处理循环引用的情况时却失效了。
看一下代码示例:
let data = {}; data.el = data; let obj1 = {}; let obj2 = {}; obj1.a = obj2; obj2.a = obj1;
具有循环引用的对象永远不会被清除作用域或被垃圾回收器回收,即使不再需要或使用它们。这会形成内存泄漏,并使应用程序效率低下。值得庆幸的是,Node.js
不再使用这种算法进行垃圾回收。
JavaScript
中的最上层对象是一个全局对象。在浏览器中,是 window
对象,但在 Nodejs
中,是 global
对象。该算法比引用计数算法更高效,并解决了循环引用的问题。
考虑到上面的例子,虽然 obj1
和 obj2 仍然
存在循环引用,但如果它们不再从顶级对象可访问(不再需要),它们将被垃圾收集。
这种算法,通常称为 mark and sweep
(标记清除算法)回收算法,非常有用。但是,你必须小心并显式地使一个对象从根节点不可访问,以确保它被垃圾收集。
这有一些方法可以提高内存使用率并避免内存泄漏。
全局变量包括使用 var
关键字声明的变量、this
关键字声明的变量和未使用关键字声明的变量。
我们已经偶然声明的全局变量(以及任何其他形式的全局变量)会导致内存泄漏。它们总是可以从全局对象访问,因此除非显式地设置为 null
,否则不能被垃圾收集。
考虑下面的例子:
function variables() { this.a = "Variable one"; var b = "Variable two"; c = "Variable three"; }
这三个变量都是全局变量。为了避免使用全局变量,可以考虑在文件顶部添加 use strict
指令来切换strict
模式。
JSON.parse
JSON
的语法比 JavaScript
简单得多,因此它比 JavaScript
对象更容易解析。
事实上,如果你使用一个大型 JavaScript
对象,通过将其转化为字符串形式,使用时解析为 JSON
,那么你可以在 V8
和Chrome
中将性能提高 1.7 倍。
在其他 JavaScript
引擎(如Safari
)中,性能可能会更好。在 Webpack
中使用这种优化方法来提高前端应用程序的性能。
例如,不使用以下 JavaScript
对象:
const Person = { name: "Samuel", age: 25, language: "English" };
更有效的方法是将它们进行字符串化,然后将其解析为JSON
。
const Person = JSON.parse('{"name":"Samuel","age":25,"language":"English"}');
你获取在实际业务中会当处理大型数据时,遇到一些奇观的内存溢出的问题,例如大的 CSV
文件。当然,你可以通过扩展你的应用内存上限去处理任务,但是最好的方法是通过将大块数据分割为多个小块(chunks
)。
在一些情况下,在多核机器上扩展 Node.js
应用程序可能会有所帮助。这涉及到将应用程序分离为主进程和工作进程。worker
处理繁重的逻辑,而 master
控制 worker
并在内存耗尽时重新启动它们。
我们创建的计时器可能会造成内存泄漏。为了提高堆内存管理,确保你的计时器不会永远运行。
特别是,使用 setInterval
创建计时器时,当不再需要计时器时调用 clearInterval
清除计时器是至关重要的。
当你不再需要使用 setTimeout
或 setimmediation
创建计时器时,调用 clearTimeout
或clearImmediate
也是一个很好的实践。
const timeout = setTimeout(() => { console.log("timeout"); }, 1500); const immediate = setImmediate(() => { console.log("immediate"); }); const interval = setInterval(() => { console.log("interval"); }, 500); clearTimeout(timeout); clearImmediate(immediate); clearInterval(interval);
在 JavaScript
中,闭包是一个常见概念。例如存在函数嵌套或者回调函数。如果在函数中使用了一个变量,当函数返回时,它将被标记为垃圾收集,但闭包可不是这样的。
代码示例:
const func = () => { let Person1 = { name: "Samuel", age: 25, language: "English" }; let Person2 = { name: "Den", age: 23, language: "Dutch" }; return () => Person2; };
上面函数会一直引用父级作用域并将每个变量保存在作用域中。换句话说,虽然你仅仅使用了 Person2
,但 Person1
和 Person2
都被保存在作用域中。
这会消耗更多内存,并造成内存泄漏。为此,在面临上面这种情况时,你最好仅声明你需要的,将不需要的重置为 null
。
例如:
const func = () => { let Person1 = { name: "Samuel", age: 25, language: "English" }; let Person2 = { name: "Den", age: 23, language: "Dutch" }; Person1 = null; return () => Person2; };
具有较长生命周期的观察器和事件发射器可能是内存泄漏的来源,特别是如果你在不再需要它们时没有取消订阅的话。
代码示例:
const EventEmitter = require("events").EventEmitter; const emitter = new EventEmitter(); const bigObject = {}; //Some big object const listener = () => { doSomethingWith(bigObject); }; emitter.on("event1", listener);
在这里,我们保留 bigObject
的内存,直到侦听器从发射器中释放,或者发射器被垃圾收集。为了解决这个问题,我们需要调用 removeEventListener
从发射器中释放监听器。
emitter.removeEventListener("event1", listener);
当连接到发射器的事件侦听器超过 10 个时,也可能发生内存泄漏。大多数情况下,你可以通过编写更高效的代码来解决这个问题。
但是,在某些情况下,你可能需要显式地设置最大事件侦听器。
例如:
emitter.setMaxListeners(n);
在这篇文章中,我们探索了如何最小化你的堆和检测 Node.js
中的内存泄漏。
我们首先研究了 Node
中的堆分配,包括堆栈和堆的工作方式。然后,我们考虑了跟踪内存使用情况和内存泄漏的原因的重要性。
接下来,我们看到了如何使用 Chrome DevTools ,
Node
的进程来查找内存泄漏。memoryUsage
API和 AppSignal
的垃圾收集可视化看板。
最后,我们发现了垃圾收集是如何工作的,并分享了一些修复应用程序内存泄漏的方法。
다른 프로그래밍 언어와 마찬가지로 JavaScript
和 Node.js
에서도 메모리 관리가 매우 중요합니다. 이 소개가 여러분에게 도움이 되기를 바랍니다. 즐거운 코딩 되세요!
원본 링크: Node.js에서 힙 할당 최소화
노드 관련 지식을 더 보려면 nodejs 튜토리얼을 방문하세요!
위 내용은 힙 할당을 최소화하고 메모리 누수를 방지하는 방법을 학습하는 노드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!