Node.js で「JavaScript heap out of memory」が発生する
V8 のヒープ上限(既定 1.5〜4GB 程度)を超えるとプロセスが落ちる。
--max-old-space-size でヒープを増やすか、ストリーム化 / リーク修正で使用量を減らす。
CI ビルドや巨大 JSON の処理で頻発。
公開: 更新:
要約
FATAL ERROR: ... JavaScript heap out of memory は V8 の old space が上限に達した とき出る。
短期的には --max-old-space-size で上限を引き上げ、根本対処として ストリーム化 と リークの特定 を行う。
CI でだけ発生する場合は実行マシンの空きメモリ自体も疑う。
よくある原因
- 上限不足: ビルドや巨大 JSON の処理で、V8 既定のヒープ上限を超える
- 一括展開:
fs.readFileSyncで巨大ファイル全体を Buffer に乗せている、JSON.parseで 100MB の配列を一気にパースしている - リーク: モジュールスコープの Map に push しっぱなし、
removeListenerし忘れ、setIntervalをclearIntervalしていない - ビルドツールのピーク: webpack / vite / tsc の型チェック・ソースマップ生成のピークがマシンを超える
解決策
1. ヒープ上限を上げる
# 一回限り
node --max-old-space-size=4096 ./script.js
# 環境変数で常時
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build公式 CLI ドキュメント(新しいタブで開く) の通り単位は MB。
物理メモリの 70〜80% を上限の目安にする。
2. ストリーム化する
import fs from "node:fs";
import readline from "node:readline";
const rl = readline.createInterface({
input: fs.createReadStream("./huge.jsonl"),
crlfDelay: Infinity,
});
for await (const line of rl) {
const row = JSON.parse(line);
process(row);
}JSON Lines や CSV はストリームと相性が良く、メモリ消費は定数化する。
3. ヒープスナップショットでリーク特定
node --inspect ./server.js
# Chrome DevTools → Memory → Take heap snapshotHeap Snapshot 公式ガイド(新しいタブで開く) の手順で 2 回スナップショットを取り、Comparison で増え続けているオブジェクトを見つける。
リスナや Map の参照を疑う。
4. ビルドツール側で抑える
- webpack:
cache: { type: "filesystem" }、devtool: falseで sourcemap を消す - vite:
build.sourcemap: false、build.rollupOptions.output.manualChunksで分割 - tsc:
--incremental+--buildで差分化、skipLibCheck: trueで型チェック節約
CI なら NODE_OPTIONS=--max-old-space-size=4096 を env に入れるのが手っ取り早い。
実行例
実際に上記の手順を node:20 環境で動かすと、ヒープ上限を 32MB に絞った状態では FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory が発生して終了コード 134 で強制終了し、--max-old-space-size=512 を指定した再実行および NODE_OPTIONS 経由どちらのケースでも 30 ブロックの確保が成功して終了コード 0 となった。
$ node --version
v20.20.2$ node --max-old-space-size=32 alloc.js
<--- Last few GCs --->
[62:0x14e7cbc0] 20 ms: Scavenge 25.9 (30.4) -> 25.9 (34.4) MB, 0.51 / 0.00 ms (average mu = 1.000, current mu = 1.000) allocation failure;
[62:0x14e7cbc0] 26 ms: Mark-Compact 33.5 (42.0) -> 33.0 (49.5) MB, 3.08 / 0.00 ms (+ 0.3 ms in 2 steps since start of marking, biggest step 0.3 ms, walltime since start of marking 10 ms) (average mu = 1.000, current mu = 1.000) finalize incremental
<--- JS stacktrace --->
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----
1: 0xb78db3 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [node]
2: 0xee8300 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node]
3: 0xee85e7 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node]
4: 0x10fb205 [node]
5: 0x1113088 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
6: 0x1114ffa v8::internal::Heap::HandleGCRequest() [node]
7: 0x1080237 v8::internal::StackGuard::HandleInterrupts() [node]
8: 0x1523dda v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) [node]
9: 0x195e476 [node]
Aborted (core dumped)
終了コード: 134$ node --max-old-space-size=512 alloc.js
全 block の確保に成功: 30 blocks
終了コード: 0$ NODE_OPTIONS="--max-old-space-size=512" node alloc.js
全 block の確保に成功: 30 blocks
終了コード: 0— 2026-08-02 時点の出力