🟢 Node.js Q60 / 118

How to detect memory leaks in Node.js?

AI-Powered Answer ✓ Answered

Memory leaks in Node.js applications can lead to degraded performance, increased latency, and eventual application crashes. Detecting and diagnosing them requires understanding Node.js's memory model and utilizing various profiling tools and techniques. This guide outlines several effective methods to identify and pinpoint memory leaks.

Understanding Memory Usage with `process.memoryUsage()`

The process.memoryUsage() method provides a simple, built-in way to get an immediate snapshot of the process's memory footprint. While it doesn't pinpoint leaks directly, it's excellent for monitoring overall trends and detecting suspicious spikes over time.

javascript
function printMemoryUsage() {
  const memory = process.memoryUsage();
  console.log(`RSS: ${Math.round(memory.rss / 1024 / 1024 * 100) / 100} MB`);
  console.log(`Heap Total: ${Math.round(memory.heapTotal / 1024 / 1024 * 100) / 100} MB`);
  console.log(`Heap Used: ${Math.round(memory.heapUsed / 1024 / 1024 * 100) / 100} MB`);
  console.log(`External: ${Math.round(memory.external / 1024 / 1024 * 100) / 100} MB`);
  console.log(`Array Buffers: ${Math.round(memory.arrayBuffers / 1024 / 1024 * 100) / 100} MB`);
}

setInterval(printMemoryUsage, 5000);

Key metrics from process.memoryUsage(): - rss (Resident Set Size): Total memory allocated to the process, including code, stack, and heap. - heapTotal: Total size of the V8 JavaScript heap (including free space). - heapUsed: Actual memory used by objects in the V8 JavaScript heap. - external: Memory used by C++ objects bound to JavaScript objects, but not managed by V8 heap (e.g., Buffers). - arrayBuffers: Memory allocated for ArrayBuffers and SharedArrayBuffers.

Detailed Analysis with Chrome DevTools

Chrome DevTools provides powerful profiling capabilities that can be connected to a running Node.js process using the --inspect flag. Start your Node.js application with node --inspect index.js and open chrome://inspect in your browser, then click 'Open dedicated DevTools for Node'.

Heap Snapshots

Heap snapshots capture a detailed view of all objects in memory at a specific point in time. By taking multiple snapshots at different stages (e.g., before and after a suspect operation, or over a period), you can compare them to identify objects that are growing in number or size, indicating a leak.

  • In DevTools, go to the 'Memory' tab.
  • Select 'Heap Snapshot' and click 'Take snapshot'.
  • Perform actions in your application that you suspect might cause a leak.
  • Take a second (and potentially third) snapshot.
  • Compare the snapshots. Look for 'Object' and 'Retained Size' columns, filtered by 'Objects allocated between Snapshot 1 and Snapshot 2' (or similar filters), to find objects that are continuously increasing without being garbage collected.

Allocation Instrumentation on Timeline

This feature records all memory allocations in real-time, showing when and where memory is allocated over a period. It's useful for identifying 'bursts' of allocations or persistent allocations that are not being deallocated, giving a timeline view of memory activity.

  • In DevTools 'Memory' tab, select 'Allocation Instrumentation on Timeline'.
  • Start recording.
  • Interact with your application.
  • Stop recording and analyze the timeline. Spikes in the chart or objects remaining at the top of the 'Constructor' list after garbage collection runs can indicate a leak.

Programmatic Heap Dumps with `heapdump`

For production environments or automated testing where interactive DevTools might not be feasible, the heapdump package allows you to programmatically generate V8 heap snapshots from a running Node.js process. These .heapsnapshot files can then be loaded and analyzed in Chrome DevTools later.

javascript
// First, install it: npm install heapdump

const heapdump = require('heapdump');

// Generate a heap snapshot on demand (e.g., via an API endpoint or signal)
process.on('SIGUSR2', () => {
  console.log('Generating heap dump...');
  heapdump.writeSnapshot((err, filename) => {
    if (err) {
      console.error('Heap dump failed:', err);
    } else {
      console.log('Heap dump written to', filename);
    }
  });
});

// Alternatively, generate periodically
// setInterval(() => {
//   heapdump.writeSnapshot();
// }, 60 * 60 * 1000); // Every hour

// Your application logic here

Once a .heapsnapshot file is generated, you can load it into Chrome DevTools (Memory tab -> Load button) for analysis, similar to interactively captured snapshots.

Advanced Profiling with `clinic.js`

clinic.js is a suite of powerful diagnostic tools for Node.js. Specifically, Clinic Doctor analyzes overall health and can hint at memory issues, while Clinic HeapSnapshot provides a more integrated and user-friendly way to take and analyze heap snapshots compared to raw DevTools.

  • Clinic Doctor: Run clinic doctor -- node your-app.js. It produces a report that highlights common performance bottlenecks, including areas related to memory pressure or excessive garbage collection.
  • Clinic HeapSnapshot: Run clinic heapsnapshot -- node your-app.js. This tool allows you to take heap snapshots at specific intervals or on demand (e.g., via HTTP requests) and visualizes the changes in memory over time, making it easier to spot growth.

Common Causes of Memory Leaks in Node.js

While tools help identify *where* memory is leaking, understanding common patterns can help you anticipate and prevent them:

  • Unclosed/Excessive Event Listeners: Attaching event listeners that are never removed can lead to objects remaining in memory indefinitely. Always remember to removeListener or use once for single-shot events.
  • Global Caches or Mismatched Lifecycles: Storing large or ever-growing data structures in global variables or long-lived caches without proper eviction policies.
  • Closures Capturing Large Scopes: Functions that close over variables from their outer scope can unintentionally keep large objects alive if the inner function outlives the outer scope (e.g., an interval callback referencing a large object that should have been garbage collected).
  • **Timers (setInterval, setTimeout) without clear***: If setInterval or setTimeout callbacks reference objects that should be garbage collected, but the timer is never cleared, those objects will persist.
  • Large Objects Held by process or other long-lived scopes: Assigning large objects to process.something or similar global/long-lived objects can prevent them from being garbage collected.