ヒープスナップショットの使用

実行中のアプリケーションからヒープスナップショットを取得し、Chrome デベロッパーツールに読み込むことで、特定の変数を検査したり、リテイナーサイズを確認したりできます。また、複数のスナップショットを比較して、時間経過による差異を確認することもできます。

警告

スナップショットを作成する際、メインスレッドの他のすべての処理は停止します。ヒープの内容によっては、1分以上かかることもあります。スナップショットはメモリ上に構築されるため、ヒープサイズが2倍になり、メモリをすべて使い果たしてアプリがクラッシュする可能性があります。

本番環境でヒープスナップショットを取得する場合は、スナップショットを取得するプロセスがクラッシュしてもアプリケーションの可用性に影響がないことを確認してください。

方法

ヒープスナップショットの取得

ヒープスナップショットを取得するには複数の方法があります

  1. インスペクター経由で、
  2. 外部シグナルとコマンドラインフラグ経由で、
  3. プロセス内での writeHeapSnapshot 呼び出し経由で、
  4. インスペクタープロトコル経由で。

1. インスペクターでメモリプロファイリングを使用する

アクティブにメンテナンスされているすべての Node.js バージョンで動作します

--inspect フラグを付けて node を実行し、インスペクターを開きます。 インスペクターを開く

ヒープスナップショットを取得する最も簡単な方法は、ローカルで実行中のプロセスにインスペクターを接続することです。その後、メモリタブに移動してヒープスナップショットを取得します。

take a heap snapshot

2. --heapsnapshot-signal フラグを使用する

v12.0.0 以降で動作します

node をコマンドラインフラグ付きで起動し、シグナルに応答してヒープスナップショットを作成できるようにすることができます。

$ node --heapsnapshot-signal=SIGUSR2 index.js

詳細については、heapsnapshot-signal フラグの最新のドキュメントを参照してください。

3. writeHeapSnapshot 関数を使用する

v11.13.0 以降で動作します。古いバージョンでは heapdump パッケージで動作します。

サーバー上で実行されているアプリケーションなど、動作中のプロセスからスナップショットが必要な場合は、以下の方法で取得を実装できます。

('v8').();

ファイル名のオプションについては、writeHeapSnapshot のドキュメントを確認してください。

プロセスを停止せずに呼び出す方法が必要なので、HTTP ハンドラ内で呼び出すか、オペレーティングシステムからのシグナルへの反応として呼び出すことが推奨されます。スナップショットをトリガーする HTTP エンドポイントを公開しないように注意してください。他の誰からもアクセスできないようにする必要があります。

v11.13.0 より前のバージョンの Node.js では、heapdump パッケージを使用できます。

4. インスペクタープロトコルを使用してヒープスナップショットをトリガーする

インスペクタープロトコルを使用して、プロセス外部からヒープスナップショットをトリガーできます。

API を使用するために、Chromium から実際のインスペクターを実行する必要はありません。

以下は、websocatjq を使用した bash でのスナップショットトリガーの例です。

#!/bin/bash
set -e

kill -USR1 "$1"
rm -f fifo out
mkfifo ./fifo
websocat -B 10000000000 "$(curl -s https://:9229/json | jq -r '.[0].webSocketDebuggerUrl')" < ./fifo > ./out &
exec 3>./fifo
echo '{"method": "HeapProfiler.enable", "id": 1}' > ./fifo
echo '{"method": "HeapProfiler.takeHeapSnapshot", "id": 2}' > ./fifo
while jq -e "[.id != 2, .result != {}] | all" < <(tail -n 1 ./out); do
  sleep 1s
  echo "Capturing Heap Snapshot..."
done

echo -n "" > ./out.heapsnapshot
while read -r line; do
  f="$(echo "$line" | jq -r '.params.chunk')"
  echo -n "$f" >> out.heapsnapshot
  i=$((i+1))
done < <(cat out | tail -n +2 | head -n -1)

exec 3>&-

以下は、インスペクタープロトコルで使用できるメモリプロファイリングツールの一部リストです。

ヒープスナップショットでメモリリークを見つける方法

2つのスナップショットを比較することでメモリリークを見つけることができます。スナップショットの差分に不要な情報が含まれていないことを確認することが重要です。以下の手順で、スナップショット間のクリーンな差分を生成できます。

  1. プロセスがすべてのソースをロードし、ブートストラップを完了するのを待ちます。最大でも数秒で完了するはずです。
  2. メモリリークが疑われる機能の使用を開始します。おそらく、リークしているものではない初期のアロケーションが発生します。
  3. 1つ目のヒープスナップショットを取得します。
  4. しばらくの間、できれば他の処理を間に挟まずに、その機能を使用し続けます。
  5. 2つ目のヒープスナップショットを取得します。2つの差分には、主にリークしていたものが含まれているはずです。
  6. Chromium/Chrome のデベロッパーツールを開き、メモリタブに移動します。
  7. 最初に古いスナップショットファイルをロードし、次に新しいものをロードします。 ツール内の読み込みボタン
  8. 新しいスナップショットを選択し、上部のドロップダウンでモードをサマリーから比較に切り替えます。 比較ドロップダウン
  9. 大きな正の差分を探し、下部パネルでそれらを引き起こした参照を調査します。

このヒープスナップショット演習で、ヒープスナップショットの取得とメモリリークの発見を練習できます。