TL;DR — 即効修正
ヒープを拡張してスクリプトを実行する:
node --max-old-space-size=4096 your-script.js
または環境変数で設定する — コマンドを直接編集しにくいビルドツールに便利:
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build
4096(4 GB)はマシンのスペックに合わせて変更してください。よく使われる値:2048、4096、8192。
エラーの原因
Node.jsはV8エンジン上で動作しており、暴走プロセスを防ぐためにヒープメモリに上限が設けられています。64ビットシステムではデフォルト上限が約1.5 GB、32ビットシステムでは約512 MBです。この上限を超えると、プロセスは即座に以下のエラーで終了します:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
主な原因:
- 大規模なフロントエンドプロジェクトのビルド — webpack、Next.js、Create React Appはこのエラーが起きやすい
- 大きなファイルやデータセットを丸ごとメモリに読み込んで処理する
- メモリリーク:オブジェクトが蓄積し続け、ガベージコレクションされない
- 正しく終了しない再帰関数
- 複数の重い処理を同時実行する
修正1:V8ヒープ上限を増やす
まずここから試してください。1行の変更で即座にブロックが解除されることがほとんどです。
コマンドに直接指定する
node --max-old-space-size=4096 server.js
NODE_OPTIONS経由(ビルドスクリプトに推奨)
# Linux / macOS
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build
# Windows(コマンドプロンプト)
set NODE_OPTIONS=--max-old-space-size=4096
npm run build
# Windows(PowerShell)
$env:NODE_OPTIONS="--max-old-space-size=4096"
npm run build
package.jsonのscriptsに設定する
{
"scripts": {
"build": "node --max-old-space-size=4096 node_modules/.bin/webpack",
"start": "node --max-old-space-size=4096 server.js"
}
}
適切な値の選び方
利用可能なRAMの約75%を目安にしてください。まず空きメモリを確認しましょう:
# Linux
free -m
# macOS
sysctl hw.memsize
# Windows
wmic OS get TotalVisibleMemorySize
8 GBのマシンなら、--max-old-space-size=6144が適切な上限です。OSや他のプロセスにも余裕が必要なため、全量を割り当てないようにしてください。
修正2:メモリリークを見つけて修正する
ヒープを増やすのは応急処置にすぎません。プロセスが際限なく肥大化し続ける場合、より高い上限でも再びクラッシュします。リークの原因を突き止める必要があります。
ヒープスナップショットを取得する
node --inspect your-script.js
Chromeでchrome://inspectを開き、Open dedicated DevTools for NodeをクリックしてMemoryタブに移動します。スナップショットを取得し、アプリをしばらく動かしてからもう一度取得します。スナップショット間でオブジェクトの種類が増え続けているものを探しましょう — それがリークの候補です。
ヒープ使用量をリアルタイムで監視する
const v8 = require('v8');
setInterval(() => {
const stats = v8.getHeapStatistics();
const usedMB = Math.round(stats.used_heap_size / 1024 / 1024);
const totalMB = Math.round(stats.heap_size_limit / 1024 / 1024);
console.log(`ヒープ: ${usedMB} MB 使用中 / ${totalMB} MB 上限`);
}, 5000);
数値を観察してください。正常なプロセスはウォームアップ後に横ばいになります。usedMBが上限なく増え続ける場合、不要な参照を保持しているものがあります。
よくあるリークのパターン
- イベントリスナーの削除忘れ — リスナーが不要になったら
emitter.removeListener()またはemitter.off()を呼ぶ - 大きなオブジェクトをキャプチャしたクロージャ — 大きな変数を参照する長命なコールバックはガベージコレクターによる解放を妨げる
- 無制限のインメモリキャッシュ — プレーンオブジェクトやMapを
lru-cacheのようなサイズ制限付きキャッシュに置き換える - 未処理のPromise rejection — catchされないrejectedなPromiseが静かに蓄積し続ける
# 軽量メモリプロファイラーをインストール
npm install --save-dev clinic
npx clinic heapprofiler -- node your-script.js
修正3:データを一括ではなくストリームで処理する
500 MBのファイルを変数に丸ごと読み込むと、このクラッシュが即座に発生します。ストリームはデータを読みながら逐次破棄するため、ヒープ使用量を一定に保てます:
const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('large-file.txt'),
crlfDelay: Infinity
});
rl.on('line', (line) => {
// 1行ずつ処理 — 巨大な配列をメモリに持たない
processLine(line);
});
配列を扱う必要がある場合は、チャンク単位で処理しましょう:
async function processInChunks(array, chunkSize, fn) {
for (let i = 0; i < array.length; i += chunkSize) {
const chunk = array.slice(i, i + chunkSize);
await fn(chunk);
}
}
チャンクサイズは100〜500件が目安です — スループットを高く保ちながらメモリの急増を防げます。
修正の確認
ビルドを再実行する前に、新しい上限が実際に有効になっているか確認してください:
node -e "const v8 = require('v8'); console.log(v8.getHeapStatistics().heap_size_limit / 1024 / 1024 + ' MB');"
# --max-old-space-size=4096 設定後の期待される出力:
# 4096 MB
クラッシュしていたコマンドを再実行してください。より高い上限でもまだ失敗する場合、根本原因はメモリリークです — 修正2に戻りましょう。
参考リソース
- Node.js CLIオプション —
node --help | grep max-old-space-size - V8ヒーププロファイリング: Node.js Diagnostics Guide
- 高度なプロファイリング用
clinicツールキット: clinicjs.org

