できない.dev

Node.js で「JavaScript heap out of memory」が発生する

V8 のヒープ上限(既定 1.5〜4GB 程度)を超えるとプロセスが落ちる。
--max-old-space-size でヒープを増やすか、ストリーム化 / リーク修正で使用量を減らす。
CI ビルドや巨大 JSON の処理で頻発。

公開: 更新:

要約

FATAL ERROR: ... JavaScript heap out of memoryV8 の old space が上限に達した とき出る。
短期的には --max-old-space-size で上限を引き上げ、根本対処として ストリーム化リークの特定 を行う。
CI でだけ発生する場合は実行マシンの空きメモリ自体も疑う。

よくある原因

  1. 上限不足: ビルドや巨大 JSON の処理で、V8 既定のヒープ上限を超える
  2. 一括展開: fs.readFileSync で巨大ファイル全体を Buffer に乗せている、JSON.parse で 100MB の配列を一気にパースしている
  3. リーク: モジュールスコープの Map に push しっぱなし、removeListener し忘れ、setIntervalclearInterval していない
  4. ビルドツールのピーク: 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 snapshot

Heap Snapshot 公式ガイド(新しいタブで開く) の手順で 2 回スナップショットを取り、Comparison で増え続けているオブジェクトを見つける。
リスナや Map の参照を疑う。

4. ビルドツール側で抑える

  • webpack: cache: { type: "filesystem" }devtool: false で sourcemap を消す
  • vite: build.sourcemap: falsebuild.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 時点の出力

この記事は役立ちましたか?