できない.dev

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

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

#node#memory#heap#v8#build

要約

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 に入れるのが手っ取り早い。

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