This fatal error occurs when a Node.js process attempts to allocate more memory than the available heap size allows. The process crashes because it cannot continue execution without the requested memory allocation.
This error indicates that your Node.js application has exhausted its allocated heap memory and cannot allocate additional memory for JavaScript objects. Node.js has a default maximum heap size (typically 512MB on 32-bit systems and around 1.4GB on 64-bit systems) as a V8 engine limitation. When your application tries to allocate memory beyond this limit, V8's garbage collector attempts multiple times to free up space (the "CALL_AND_RETRY_LAST" phase), and when all attempts fail, the process terminates with this fatal error. The error is particularly common during memory-intensive operations like building large applications with webpack or similar tools, processing large datasets, or when memory leaks cause gradual memory accumulation over time.
The quickest solution is to increase the maximum heap size Node.js can use. Start your application with the --max-old-space-size flag:
# Set heap size to 4GB (4096 MB)
node --max-old-space-size=4096 app.js
# For npm scripts, add to package.json
"scripts": {
"start": "node --max-old-space-size=4096 server.js",
"build": "node --max-old-space-size=4096 node_modules/.bin/webpack"
}For build tools, you may need to set the NODE_OPTIONS environment variable:
# Linux/macOS
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build
# Windows (PowerShell)
$env:NODE_OPTIONS="--max-old-space-size=4096"
npm run build
# Windows (cmd)
set NODE_OPTIONS=--max-old-space-size=4096 && npm run buildAdjust the size based on your system's available RAM. Common values are 2048 (2GB), 4096 (4GB), or 8192 (8GB).
For a persistent solution, set NODE_OPTIONS in your environment:
Linux/macOS (.bashrc, .zshrc, or .profile):
export NODE_OPTIONS="--max-old-space-size=4096"Windows (System Environment Variables):
1. Open System Properties → Advanced → Environment Variables
2. Add a new System Variable:
- Variable name: NODE_OPTIONS
- Variable value: --max-old-space-size=4096
Docker/Container environments:
ENV NODE_OPTIONS="--max-old-space-size=4096"In .env files (with dotenv):
NODE_OPTIONS=--max-old-space-size=4096Restart your terminal or application after setting environment variables.
Use Node.js profiling tools to identify memory-intensive code:
# Run with inspector enabled
node --inspect app.js
# Or with explicit memory monitoring
node --inspect --trace-gc app.jsThen open Chrome DevTools:
1. Navigate to chrome://inspect
2. Click "inspect" under your Node.js process
3. Go to the Memory tab
4. Take heap snapshots before and after operations
5. Compare snapshots to identify growing objects
Using clinic.js for automatic analysis:
npm install -g clinic
clinic doctor -- node app.jsLook for:
- Objects that grow over time without being released
- Event listeners that aren't removed
- Cached data without size limits
- Unclosed database connections or file handles
Refactor memory-intensive operations:
Use streaming instead of loading entire files:
// Bad: loads entire file into memory
const data = fs.readFileSync('large-file.json', 'utf8');
const json = JSON.parse(data);
// Good: streams data in chunks
const stream = fs.createReadStream('large-file.json');
const parser = JSONStream.parse('*');
stream.pipe(parser);
parser.on('data', (chunk) => {
// Process each chunk
});Implement pagination for large datasets:
// Bad: loads all records
const allRecords = await db.users.findMany();
// Good: processes in batches
const pageSize = 100;
let page = 0;
while (true) {
const records = await db.users.findMany({
skip: page * pageSize,
take: pageSize
});
if (records.length === 0) break;
// Process records
page++;
}Clear references and use WeakMap for caches:
// Use WeakMap for automatic garbage collection
const cache = new WeakMap();
// Explicitly null out large objects when done
largeObject = null;Ensure proper cleanup of event listeners and intervals:
// Bad: event listener never removed
eventEmitter.on('data', handler);
// Good: remove when done
eventEmitter.on('data', handler);
// Later...
eventEmitter.removeListener('data', handler);
// Better: use once() for single-use listeners
eventEmitter.once('data', handler);
// Clear intervals and timeouts
const interval = setInterval(() => {}, 1000);
// Later...
clearInterval(interval);
// Use AbortController for cleanup
const controller = new AbortController();
eventEmitter.on('data', handler, { signal: controller.signal });
// Later...
controller.abort(); // Removes all listenersCheck for circular references:
// Bad: creates circular reference
const obj1 = {};
const obj2 = { ref: obj1 };
obj1.ref = obj2; // Circular reference
// Good: avoid or break circular references
obj1.ref = null; // Break the cycle when doneFor build-time errors, optimize webpack/vite configuration:
webpack.config.js:
module.exports = {
mode: 'production',
optimization: {
minimize: true,
usedExports: true, // Tree shaking
splitChunks: {
chunks: 'all', // Split large bundles
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
// Reduce memory usage during builds
stats: 'errors-only',
performance: {
hints: false
}
};vite.config.js:
export default {
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
}
}
}
}
};Understanding V8 memory structure: V8's heap is divided into spaces: New Space (for short-lived objects), Old Space (for long-lived objects), Large Object Space, and Code Space. The --max-old-space-size flag specifically controls the Old Space limit, which is typically where memory issues occur as objects are promoted from New Space.
OS-level memory limits: On Linux systems, check ulimit settings that may restrict memory: ulimit -v shows virtual memory limits. Container environments (Docker, Kubernetes) have their own memory limits that may be lower than Node.js heap settings.
32-bit vs 64-bit considerations: 32-bit Node.js processes cannot address more than ~1.5GB of memory regardless of --max-old-space-size settings. Always use 64-bit Node.js for memory-intensive applications.
Alternative memory management flags:
- --max-semi-space-size: Controls New Space size (rarely needed)
- --optimize-for-size: Reduces memory footprint at the cost of performance
- --expose-gc: Allows manual garbage collection via global.gc()
Monitoring production memory: Use tools like PM2, New Relic, or Datadog to monitor memory trends in production. Set up alerts for memory usage above 80% to catch issues before crashes occur.
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files