ホームページ  >  記事  >  ウェブフロントエンド  >  ヒープ割り当てを最小限に抑え、メモリ リークを防ぐ方法を学習するノード

ヒープ割り当てを最小限に抑え、メモリ リークを防ぐ方法を学習するノード

青灯夜游
青灯夜游転載
2023-01-11 20:25:392161ブラウズ

Node.js でメモリ リークをチェックするにはどうすればよいですか?次の記事では、Nodejs のヒープ割り当てについて説明し、ヒープ割り当てを最小限に抑えてメモリ リークを防ぐ方法を紹介します。

ヒープ割り当てを最小限に抑え、メモリ リークを防ぐ方法を学習するノード

#メモリ管理の問題は、コンピュータ分野で常に大きな注目を集めてきました。コンピュータ上で実行される各ソフトウェアには、コンピュータの限られたメモリのごく一部が割り当てられます。このメモリは慎重に管理し、適切なタイミングで割り当てまたは解放する必要があります。

Nodejs は、効率的な自動ガベージ コレクション メカニズムを通じてメモリ管理の退屈なタスクを処理できるため、開発者は他のタスクに取り組むことができます。 Nodejs は開発者がメモリ管理の問題を解決するのに役立ちましたが、大規模なアプリケーションを開発する過程では、開発者が V8Nodejs を理解するのは困難です。のメモリ管理メカニズムは依然として非常に重要です。

この記事では、主にヒープ内のメモリの割り当てと解放の方法を紹介し、ヒープの割り当てを最小限に抑えてメモリ リークを防ぐ方法を理解するのに役立ちます。 [関連チュートリアルの推奨事項: nodejs ビデオ チュートリアル プログラミング教育 ]

Nodejs でのヒープ割り当て

JavaScriptNode.js は多くのことを抽象化し、ほとんどの重労働を舞台裏で実行します。

コードの一部が実行されると、コード内の変数とオブジェクトがスタック メモリまたはヒープ メモリに保存されることがわかっています。JavaScript コードは、次のコードに保存されます。実行コンテキスト内で実行されます。

ECMAScript 仕様自体は、メモリの割り当てと管理の方法を指定していません。これは、JavaScript エンジンと基盤となるシステム アーキテクチャに依存する実装の詳細です。エンジンが変数を処理する方法についての深い理解はこの記事の範囲を超えていますが、V8 がどのようにこれを行うかについて詳しく知りたい場合は、記事JavaScript メモリ モデルを参照してください。 データが V8 JS エンジン メモリにどのように保存されるかを明らかにしますか?

Node.js でヒープ メモリの効率的な使用が重要な理由?

ヒープに格納されたメモリ変数は、削除されたりしない限り常に存在します。ガベージコレクターによって解放されます。ヒープ メモリは、割り当てられて解放された後もこの状態が維持される、大きな連続したメモリ ブロックです。

残念ながら、ヒープ メモリの収集および解放の方法が原因で、メモリが無駄になり、リークが発生する可能性があります。

V8 は世代別ガベージ コレクション メカニズムを使用します。つまり、オブジェクトを異なる世代 (新世代と旧世代) に分割します。世代空間はさまざまな領域に分割されます。たとえば、新世代は新しい空間で構成され、古い世代は古い空間、マッピング空間、ラージ オブジェクト空間に分割されます。新しいオブジェクトは最初に若い世代のスペースに割り当てられ、若い世代のスペースが使い果たされると、ガベージ コレクターがクリーンアップ メカニズムを実行してスペースを解放します。 1 回の GC 実行で生き残ったオブジェクトは若い世代の中央にコピーされ、2 回目の実行で生き残ったオブジェクトは古い世代に移動されます。

実行中のプログラムは最初にメモリを収集し、貴重な仮想メモリ リソースを占有するため、メモリが不要になった場合、プログラムはメモリを解放する必要があります。これがメモリ解放です。

さらに、メモリが解放されると (以前に解放されたヒープの場所に関係なく)、ヒープ メモリは連続したメモリ ブロックにマージされます。ヒープ メモリの複雑さが増すため、ここに保存するとパフォーマンスのオーバーヘッドが高くなります (ただし、後続のストアでは柔軟性が高まります)。

Nodejs には効率的なガベージ コレクション メカニズムがありますが、ヒープ メモリの非効率的な使用によりメモリ リークが発生する可能性があります。アプリケーションがメモリを過剰に消費したり、クラッシュしたりする可能性があります。

Nodejs ヒープ メモリ リークの原因

ガベージ コレクターは孤立したメモリ領域を探して解放しますが、すべてのメモリを追跡できない場合があります。これは、特に大規模なアプリケーションの場合、不必要な負荷の増加につながる可能性があります。 Nodejs でガベージ コレクターがどのように動作するかについては、後ほど詳しく説明します。

メモリ リークの最も一般的な原因には次のものがあります。

  • 複数の参照
  • グローバル変数
  • クロージャ
  • タイマー
  • イベント

複数使用です変数ポインタを介してオブジェクトへの参照を保持する非常に一般的な操作です。これは非常に便利ですが、オブジェクトへの参照の 1 つがガベージ コレクターによって収集され、他の参照が収集されない場合、メモリ リークが発生する可能性もあります。

Node.js および JavaScript アプリケーションでは、クリーンアップを忘れられたタイマーとコールバック関数も、メモリ リークの一般的な 2 つの原因です。タイマーにバインドされたオブジェクトは、タイムアウトになるまでガベージ コレクションされません。タイマーが永久に実行されると、参照されたオブジェクトはガベージ コレクターによって収集されなくなります。これは、変数ポインタがオブジェクトを参照していない場合でも発生し、ヒープ内でメモリ リークが発生します。

サンプル コードについて考えてみましょう:

const language = () => {
  console.log("Javascript");】
  // 递归自身
  setTimeout(() => language(), 1000);
}

上記のコードは常に実行され、ガベージ コレクターによってリサイクルされることはありません

調べ方## メモリ#Nodejs

#Nodejs

でのメモリ リークの検出とデバッグに使用できるツールがいくつかあります。 ノードのプロセス。 memoryUsage API および AppSignal のガベージ コレクター ダッシュボード。 Chrome DevTools の使用

Chrome DevToolsおそらく最も簡単なツールの 1 つです。デバッガーを開始するには、

Node

inspect モードで開始する必要があります。これを行うには、node --inspect を実行します。 より具体的には、Node への入り口が

app.js

である場合、node --inspect app.js を実行して、ノードアプリケーションをデバッグします。次に、Chromium ブラウザを開き、「chrome://inspect」と入力します。 Edge://inspect でインスペクター ページを開くこともできます。インスペクター ページには、次のようなページが表示されます。

デバッグしようとしている

Node アプリケーションがインスペクター ページの下部に表示されることに注意してください。 。

inspect

をクリックしてデバッガーを開きます。デバッガーには 2 つの重要なタブ (MemoryProfiler) がありますが、この説明では Memory タブに焦点を当てます。

Chrome デバッガの使用メモリ リークを見つける最も簡単な方法は、

ヒープ スナップショット

を使用することです。スナップショットは、いくつかの変数を検査したり、その予約サイズを確認したりするのに役立ちます。 複数のスナップショットを比較することでメモリ リークを見つけることもできます。メモリ リークの前後のスナップショットを保存し、その 2 つを比較するとよいでしょう。スナップショットを取得するには、ヒープ スナップショット

を 1 回クリックしてから、*スナップショットの取得 ボタンをクリックします。アプリケーションの Total JS ヒープ サイズによっては、これには時間がかかる場合があります。 DevTool の下部にある load ボタンをクリックして、既存のスナップショットをロードすることもできます。 2 つ以上のスナップショットがある場合、ヒープ割り当てを簡単に比較し、メモリ リークの原因を見つけることができます。スナップショットは次の方法で表示できます。

    summary
  • : コンストラクター名に基づいてアプリケーション内の Node オブジェクトは次のとおりです。グループで表示

  • 比較
  • : 2つのスナップショットの違いを表示

  • Containment
  • : ヒープ内を調べ、グローバル名前空間で参照されるオブジェクトを分析できます

  • 統計
  • :

##DevTools

ヒープ アナライザーには 2 つの注目すべき列があります - つまり

Shallow 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 的版本高于 11.13。在早期的版本中,你可以使用相关的包来实现。

使用 Chrome DevTools 获取堆快照并不是调试内存问题的唯一方法。你也可以使用Allocation instrumentation on timeline 跟踪每个堆分配的情况。

内存分配时间轴显示了随时间变化的测量内存分配的情况。要启用此功能,需要先启动分析器(Profiler),然后运行应用程序示例以开始调试内存问题。如果你希望记录长时间运行的内存分配操作,并想要更小的性能开销,那么最好的选择是分配抽样方法。

通过 Nodeprocess.memoryUsage API

你也可以使用 Nodeprocess.memoryUsage API来观察内存使用情况。运行 process.memoryUsage,你可以访问以下内容:

  • rss:已分配的内存量
  • heapTotal:已分配堆的总大小
  • heapUsed:当执行进程时被使用内存总量
  • arrayBuffers:为 Buffer 实例分配的内存大小

使用 AppSignal 的垃圾收集器看板

为了可视化堆的变化情况,AppSignal 提供了一个方便的垃圾收集看板。当你将 Node.js 应用连接到AppSignal 时,这个看板会自动为你生成!

看看这个例子,在“V8 Heap Statistics”图表中,你可以清楚地看到内存使用的峰值:

如果看板中中的数据出现一个稳定增长的趋势,这意味着你的代码中或者依赖中存在内存泄漏的情况。

了解更多关于 Node.js 的AppSignal。

垃圾回收机制工作原理

如果你知道如何发现内存泄漏,但如何修复它们?我们可能很快就知道。但是首先重要的是理解 NodejsV8 是如何进行垃圾收集的。

垃圾回收机制会在不需要的时候释放内存。为了更高效的工作,垃圾回收算法必须正确的定义和识别不需要再内存中继续存储的内容。

在引用计数 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 (标记清除算法)回收算法,非常有用。但是,你必须小心并显式地使一个对象从根节点不可访问,以确保它被垃圾收集。

修复 Nodejs App 中的内存泄漏

这有一些方法可以提高内存使用率并避免内存泄漏。

避免全局变量

全局变量包括使用 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,那么你可以在 V8Chrome 中将性能提高 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 清除计时器是至关重要的。

当你不再需要使用 setTimeoutsetimmediation 创建计时器时,调用 clearTimeoutclearImmediate 也是一个很好的实践。

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,但 Person1Person2 都被保存在作用域中。

这会消耗更多内存,并造成内存泄漏。为此,在面临上面这种情况时,你最好仅声明你需要的,将不需要的重置为 null

例如:

const func = () => {
  let Person1 = { name: "Samuel", age: 25, language: "English" };
  let Person2 = { name: "Den", age: 23, language: "Dutch" };
  Person1 = null;
  return () => Person2;
};

取消订阅观察者和 Event Emitters

具有较长生命周期的观察器和事件发射器可能是内存泄漏的来源,特别是如果你在不再需要它们时没有取消订阅的话。

代码示例:

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 的垃圾收集可视化看板。

最后,我们发现了垃圾收集是如何工作的,并分享了一些修复应用程序内存泄漏的方法。

他のプログラミング言語と同様、JavaScriptNode.js ではメモリ管理が非常に重要です。この紹介がお役に立てば幸いです。コーディングを楽しんでください!

元のリンク: Node.js でのヒープ割り当ての最小化

ノード関連の知識について詳しくは、こちらをご覧ください。 nodejs チュートリアル にアクセスしてください!

以上がヒープ割り当てを最小限に抑え、メモリ リークを防ぐ方法を学習するノードの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。