【入門編】Node.jsのメモリリークをWebStormで追跡せよ!ヒープダンプ解析によるパフォーマンス改善実践 – 総合開発環境(IDE)生産性向上バイブル

こんにちは!日々の開発、本当にお疲れ様です。

フロントエンドもバックエンドもJavaScript/TypeScriptでバリバリ書いていく現代の開発において、最も頼りになる相棒といえば、やはりJetBrains社のWebStormですよね。

「なんだか最近、開発中のNode.jsサーバーやテストの動きがモッサリする…」「しばらく起動しっぱなしにしていると、メモリ使用量が右肩上がりに膨らんで最終的に落ちる…」
そんな経験、ありませんか?

これ、多くの場合は「メモリリーク(意図しないメモリの消し忘れ・参照残り)」が原因です。
今回は、世界最高峰のIDEであるWebStormのプロファイラ機能をフル活用して、この目に見えないメモリリークの犯人を鮮やかに特定し、アプリを爆速かつ安定させる方法を一緒に見ていきましょう。

これをマスターすれば、原因不明のクラッシュに怯える夜とはおさらばできますよ。それでは、さっそく扉を開けていきましょう!

—

1. なぜNode.jsはメモリリークを起こすのか?(超基礎知識)

Node.js(V8エンジン)は、使わなくなったオブジェクトを「ガベージコレクション(GC)」という仕組みで自動的に掃除してくれます。
しかし、「プログラマが意図せず、そのオブジェクトへの参照(リンク)をどこかに残し続けてしまった場合」、V8は「まだこのデータは使っているんだな」と勘違いして掃除してくれません。

これがメモリリークの正体です。

  • グローバル変数への意図しないデータ蓄積
  • イベントリスナーの登録解除忘れ(`EventEmitter`の罠)
  • クロージャ内部での不要な変数のキャプチャ

これらが積み重なると、ヒープ領域(メモリの置き場所)がパンクし、CPUがGCの処理に追われてアプリが重くなります。これを感覚ではなく、データで正確に殴って解決するのがWebStormのプロファイラです。

—

2. 準備:あえて「メモリリークするコード」で動作確認しよう

百聞は一見に如かず。まずは、わざとメモリリークを起こす小さなNode.jsスクリプトを用意して、WebStormでどう検知できるかを体験してみましょう。

プロジェクトに適当なファイル(例: `leaky-app.ts` または `leaky-app.js`)を作成し、以下のコードを貼り付けてください。

// leaky-app.ts
// 意図的にメモリリークを発生させる検証用コード

// データを蓄積するための巨大なグローバル配列
const memoryLeakArray: any[] = [];

function causeLeak() {
// 毎回1MBのダミーデータを作成して配列にプッシュし続ける
const largeData = {
data: new Array(1024 1024).fill(‘🔥’),
timestamp: new Date().toISOString()
};

memoryLeakArray.push(largeData);
console.log(`現在のリーク配列のサイズ: ${memoryLeakArray.length} MB`);
}

// 100ミリ秒ごとにメモリリークを発生させる
setInterval(causeLeak, 100);

console.log(‘— メモリリーク検証アプリが起動しました (PID: %d) —‘, process.pid);

このコードは、時間が経てば経つのにメモリを解放せず、ひたすらRAMを食いつぶしていく危険な(しかし今回の検証には最高の)子です。

—

3. WebStormのプロファイラ設定とヒープダンプの取得

それでは、このアプリをWebStormから起動し、メモリの状態をリアルタイムで覗き見してみましょう。

Step 1: 実行構成(Run Configuration)の作成

1. WebStormの右上にある「Run/Debug Configurations」を開きます。
2. `+` ボタンを押して Node.js を選択します。
3. Node parameters(またはV8のオプション)に、以下のフラグを追加しておくと、のちの解析精度が劇的に上がります。

–expose-gc

(※これにより、コード内やプロファイラから強制的にガベージコレクションを手動発動できるようになります)
4. Application fileに先ほど作成した `leaky-app.ts` を指定して保存します。

Step 2: 実行とメモリプロファイリングの開始

1. 虫アイコン(Debug)または実行ボタンでアプリを起動します。
2. 数秒経つと、コンソールに「現在のリーク配列のサイズ: X MB」と表示され始めます。
3. WebStormの「Services」タブまたは下部にある「Debug」ツールウィンドウに注目してください。
4. デバッグツールバーの中にある、「CPU/Memory Profiling」アイコン(カメや時計のようなマーク、あるいは「Memory」タブ)をクリックします。
5. ここで、「Take Heap Snapshot(ヒープスナップショットの取得)」を実行します。

数秒待つと、WebStorm内に `.heapsapshot` という拡張子のファイルが生成され、エディタ画面にメモリ内の全オブジェクトの勢力図が表示されます。

—

4. 巨大なオブジェクトと参照漏れを視覚的に特定するテクニック

ヒープスナップショットが開くと、最初は情報の多さに圧倒されるかもしれませんが、見るべきポイントはたったの2つです。焦らずいきましょう。

A. 「Retained Size(保持サイズ)」でソートする

スナップショット画面には、たくさんのコンストラクタ(クラス名やデータ型)が並んでいます。ここで重要な指標が2つあります。

  • Shallow Size: そのオブジェクト単体が占めているメモリサイズ(小さめ)。
  • Retained Size: そのオブジェクトが消えることで、連鎖的に解放されるメモリの合計サイズ(超重要!)。

「Retained Size」の列をクリックして、降順(大きい順)にソートしてください。
今回のサンプルコードであれば、`Array` や `string` が上位にドンと鎮座しているはずです。これが犯人の足跡です。

B. 「Path to Root(ルートへのパス)」で参照の根源を断つ

「何が原因でこのオブジェクトがメモリに残っているのか」を突き止めるには、下部ペインにある 「Path to Root」 を見ます。

ここには、「どの変数からグローバルスコープ(Root)に向かって繋がっているか」という参照の鎖がツリー状に表示されます。

  • `leaky-app.ts` の中の `memoryLeakArray` という名前のグローバル変数から線が伸びていることが、これで完全に視覚化されます。

「あ、ここで配列にpushされたまま、どこからもpopされてないんだな」という原因が、コードと完全にリンクする瞬間です。この「あ、わかった!」という瞬間が、エンジニアにとって最高に気持ちいい瞬間ですよね。

—

5. 修正とメモリ解放の確認(ビフォー・アフター)

原因がわかったら、コードを修正して本当にメモリが綺麗に解放されるか確認しましょう。

修正コードの適用

例えば、メモリを適度にクリアするロジック(あるいは参照を切る実装)に書き換えます。

// leaky-app-fixed.ts
const memoryLeakArray: any[] = [];

function causeNoLeak() {
const largeData = {
data: new Array(1024 1024).fill(‘✨’),
timestamp: new Date().toISOString()
};

memoryLeakArray.push(largeData);

// 配列が大きくなりすぎたら古いものをシフトして解放する(参照を切る)
if (memoryLeakArray.length > 5) {
memoryLeakArray.shift(); // 先頭を削除=参照が切れてGCの対象になる
}
}

setInterval(causeNoLeak, 100);

再度スナップショットを取って比較する

修正版でアプリを再起動し、再度WebStormで「Take Heap Snapshot」を取得します。
さらに、WebStormのプロファイラ画面にある「Compare with Snapshot(スナップショットの比較)」機能を使ってみてください。

修正前と修正後のメモリの差分(Diff)が計算され、「減ったオブジェクト」「増えたオブジェクト」が赤や緑でハイライトされます。
余計なオブジェクトの蓄積がピタッと止まり、メモリグラフが安定(フラット)しているのを確認できた瞬間、パフォーマンス改善のミッションは完了です!

—

先輩エンジニアからの実践アドバイス

実務の現場では、もっと複雑な非同期処理やクロージャ、外部ライブラリ(RxJSや各種ORMなど)が絡み合ってメモリリークが発生します。そんなときでも、WebStormのヒープスナップショット機能があれば、迷子になることはありません。

  • ポイント: 本番環境(Production)で重いと感じたら、ステージング環境やローカルで `–inspect` フラグつきでNodeを立ち上げ、WebStormからリモートデバッグ接続してヒープダンプを取るのがプロの常道です。

「アプリが重い」というフワッとした課題を、データとIDEの力で論理的にねじ伏せる。このスキルが身につけば、あなたのバックエンド・フロントエンド開発の自信は揺るぎないものになります。

毎日のコーディングが、もっと楽しく、もっとスマートになりますように。
それでは、次の開発もハッピーにいきましょう!

タイトルとURLをコピーしました