Sửa lỗi 'FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory' trong Node.js

intermediate💚 Node.js2026-03-18| Node.js (tất cả phiên bản), Linux / macOS / Windows, mọi ứng dụng hoặc build tool Node.js (webpack, Next.js, v.v.)

Error Message

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
#nodejs#memory#heap#v8#performance

TL;DR — Sửa Nhanh

Chạy script với heap lớn hơn:

node --max-old-space-size=4096 your-script.js

Hoặc đặt qua biến môi trường — tiện lợi cho các build tool khi bạn không thể sửa lệnh trực tiếp:

export NODE_OPTIONS="--max-old-space-size=4096"
npm run build

Thay 4096 (4 GB) bằng giá trị phù hợp với máy của bạn. Các giá trị phổ biến: 2048, 4096, 8192.

Nguyên Nhân Gây Ra Lỗi

Node.js chạy trên engine V8, vốn giới hạn bộ nhớ heap để ngăn các tiến trình chiếm dụng tài nguyên quá mức. Trên hệ thống 64-bit, giới hạn mặc định vào khoảng 1,5 GB; trên hệ thống 32-bit, khoảng 512 MB. Vượt ngưỡng đó, tiến trình sẽ chết ngay lập tức với thông báo:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

Các nguyên nhân thường gặp:

  • Build các dự án front-end lớn — webpack, Next.js và Create React App nổi tiếng hay gây ra lỗi này
  • Xử lý file hoặc tập dữ liệu lớn hoàn toàn trong bộ nhớ
  • Memory leak: các object cứ tích lũy mãi và không bao giờ được garbage collector thu hồi
  • Hàm đệ quy không kết thúc đúng cách
  • Chạy nhiều tác vụ nặng cùng một lúc

Cách Sửa 1: Tăng Giới Hạn Heap V8

Bắt đầu từ đây. Chỉ một dòng lệnh và thường giải quyết ngay vấn đề.

Trực tiếp trong lệnh

node --max-old-space-size=4096 server.js

Qua NODE_OPTIONS (khuyến nghị cho build script)

# Linux / macOS
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build

# Windows (Command Prompt)
set NODE_OPTIONS=--max-old-space-size=4096
npm run build

# Windows (PowerShell)
$env:NODE_OPTIONS="--max-old-space-size=4096"
npm run build

Trong scripts của package.json

{
  "scripts": {
    "build": "node --max-old-space-size=4096 node_modules/.bin/webpack",
    "start": "node --max-old-space-size=4096 server.js"
  }
}

Chọn giá trị phù hợp

Nhắm vào khoảng 75% RAM khả dụng của máy. Kiểm tra trước xem bạn có bao nhiêu:

# Linux
free -m

# macOS
sysctl hw.memsize

# Windows
wmic OS get TotalVisibleMemorySize

Với máy 8 GB RAM, --max-old-space-size=6144 là mức trần hợp lý. Đừng cấp phát toàn bộ — hệ điều hành và các tiến trình khác cũng cần bộ nhớ.

Cách Sửa 2: Tìm và Vá Memory Leak

Tăng heap chỉ là giải pháp tạm thời. Nếu tiến trình cứ tăng mãi không dừng, nó sẽ crash lại — chỉ là ở ngưỡng cao hơn thôi. Bạn cần truy tìm nguồn gốc của leak.

Tạo heap snapshot

node --inspect your-script.js

Mở chrome://inspect trong Chrome, nhấn Open dedicated DevTools for Node, rồi vào tab Memory. Chụp snapshot, để app chạy một lúc, rồi chụp thêm một cái nữa. Tìm các kiểu object cứ tăng lên giữa các snapshot — đó là ứng viên leak của bạn.

Theo dõi mức dùng heap theo thời gian thực

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(`Heap: ${usedMB} MB đang dùng / ${totalMB} MB giới hạn`);
}, 5000);

Quan sát các con số. Một tiến trình khỏe mạnh sẽ ổn định sau khi khởi động xong. Nếu usedMB cứ tăng mãi không có điểm dừng, có gì đó đang giữ tham chiếu không cần thiết.

Các pattern leak phổ biến

  • Event listener không được gỡ bỏ — gọi emitter.removeListener() hoặc emitter.off() khi listener không còn cần thiết nữa
  • Closure giữ các object lớn — callback tồn tại lâu mà tham chiếu đến biến lớn sẽ ngăn garbage collector giải phóng chúng
  • Cache trong bộ nhớ không có giới hạn kích thước — thay plain object hoặc Map bằng cache có giới hạn size như lru-cache
  • Promise rejection không được xử lý — các promise bị reject mà không được catch có thể âm thầm tích lũy theo thời gian
# Cài memory profiler nhẹ
npm install --save-dev clinic
npx clinic heapprofiler -- node your-script.js

Cách Sửa 3: Xử Lý Dữ Liệu Bằng Stream, Không Đọc Tất Cả Vào Bộ Nhớ

Nạp file 500 MB vào một biến là công thức chắc chắn gây crash. Stream đọc và bỏ dữ liệu ngay khi xử lý — mức dùng heap luôn ổn định:

const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
  input: fs.createReadStream('large-file.txt'),
  crlfDelay: Infinity
});

rl.on('line', (line) => {
  // xử lý từng dòng một — không có mảng khổng lồ trong bộ nhớ
  processLine(line);
});

Khi buộc phải làm việc với mảng, hãy xử lý theo từng batch:

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);
  }
}

Kích thước chunk từ 100–500 phần tử là điểm khởi đầu tốt — đủ để duy trì throughput cao mà không làm bộ nhớ tăng đột biến.

Kiểm Tra Sau Khi Sửa

Xác nhận giới hạn mới đã có hiệu lực trước khi chạy lại build:

node -e "const v8 = require('v8'); console.log(v8.getHeapStatistics().heap_size_limit / 1024 / 1024 + ' MB');"

# Sau khi đặt --max-old-space-size=4096, kết quả mong đợi:
# 4096 MB

Chạy lại lệnh trước đó đang bị crash. Nếu vẫn thất bại ở giới hạn cao hơn, nguyên nhân gốc rễ là memory leak — quay lại Cách Sửa 2.

Đọc Thêm

Related Error Notes