Memory Leak and Heap Growth Diagnosis

Memory leak diagnosis starts by asking which memory is growing under which workload, and whether that growth should end. A rising chart can represent retained objects, a useful cache, larger in-flight work, native allocations, or a runtime keeping committed pages for reuse.

Compare equivalent workload phases, identify the growing object population, and trace why it remains reachable. Increasing the heap limit can postpone failure; it does not explain the reference that keeps yesterday’s requests alive.

TL;DR

Quick Example

Save as retention.cjs and run with node --expose-gc retention.cjs in a disposable local process. This complete bounded example deliberately retains 2,000 fictional request objects and then releases the map.

Counts demonstrate the reference lifecycle. Byte totals are observations from that process, not fixed assertions or a production leak threshold. Forced garbage collection is a fixture convenience, not a proposed request-path treatment.

Core Concepts

Allocation and retention

An allocation profile answers where objects were created over an interval. A heap snapshot describes reachable heap objects at capture time; retaining paths explain references preventing reclamation. A hot allocation site may produce short-lived objects correctly, while a small global registry retains them indefinitely.

Heap and process memory

Node exposes heapUsed, heapTotal, external, arrayBuffers, and rss with different meanings. Array-buffer memory is included within external memory, so do not add every field as independent usage. RSS covers more than the JavaScript heap. Node process memory accounting

Expected working set

A cache populated by new keys may grow until its configured bound. A backlog may retain live request data until processing catches up. Both can cause an outage even if their references follow the intended code. The diagnosis must distinguish an incorrect lifetime from an insufficiently bounded design.

Follow a repeatable investigation

First identify the failure: process termination, container limit, rising pause time, allocation failure, or host pressure. Preserve the clock, runtime version, workload, concurrency, request size, and relevant memory measurements. Different clocks or processes can produce a convincing but false trend.

Warm up the service, repeat a bounded workload, let in-flight work finish, and compare corresponding points. Track object counts or cache entries alongside memory. If the count grows with every completed request and never expires, inspect ownership and cleanup. If it stabilizes, extend the test enough to challenge the intended bound before calling it healthy.

Snapshots need an operational plan. Node warns that capture stops main-thread work and can substantially increase memory demand, potentially crashing the process. Use a disposable or safely isolated instance with headroom and a recovery path. Snapshots can contain tokens and application data; restrict access and retention. Node heap snapshot guidance

Compare captures from equivalent phases and trace a growing population to a root. In a browser, detached DOM nodes may be retained by listeners or closures. In a server, timers, request registries, and caches deserve the same ownership questions. Follow the actual references rather than blaming the framework named in the stack.

Verify the lifetime repair

Make the smallest change that corrects ownership: unsubscribe at the documented lifecycle boundary, remove completed work from the registry, bound a cache, or cancel obsolete asynchronous work. Confirm cleanup also happens on errors and cancellation. A successful response path is only one way a resource can finish.

Repeat the original workload and the failure path that previously retained data. Check object population, throughput, latency, and error behavior together. A map that stops growing because every request now fails is not a successful repair. Keep the original reproducer as a regression test when it exercises the real lifetime boundary.

The local map example proves only that the illustrated references can be released. It does not identify a leak in Angular, Node, or any application, and it does not measure browser memory, native libraries, or container behavior.

Best Practices

Preserve comparable evidence

Record workload and phase with every capture. Comparing a busy peak with an idle process can identify more live objects without identifying a leak.

Fix ownership before tuning limits

Use size limits and concurrency bounds where they express intended behavior. Adjust memory budgets only after understanding what must remain live and validating the resulting latency and recovery behavior.

Handle diagnostics as application data

Keep captures out of public issue attachments and ordinary logs. Share only the minimum evidence needed through an approved channel.

Common Mistakes

Bad: Calling any upward RSS slope a JavaScript leak.

Correct: Separate memory categories and compare retained heap populations under equivalent workload phases.

Bad: Taking repeated snapshots on the only healthy instance.

Correct: Plan capture overhead, isolation, data handling, and recovery first.

Bad: Clearing a cache without checking correctness.

Correct: Verify the new lifetime preserves required results and remains bounded under repeat and failure cases.

FAQ

Does garbage collection prevent leaks?

It reclaims unreachable managed objects. Objects that remain reachable through an unintended root can still accumulate.

Should RSS immediately fall after releasing objects?

Do not require that as a universal rule. Use runtime-specific accounting, object evidence, and repeated workload behavior.

What confirms the fix?

The original retention pattern stops under comparable repeated work, while correctness and service objectives still hold.

Related Topics

References