Skip to content

18 — Memory and garbage collection

One line: Node frees an object when nothing can reach it any more, not when you are done with it, so every leak is a reference you forgot, and collection is something a program can observe but never schedule.

The chapter starts with reachability, the rule the collector follows, shown with a WeakRef and a forced gc(). It then turns the rule around to explain leaks: a closure's shared scope, listeners that pile up, a cache that never empties. WeakRef and FinalizationRegistry come next, with the reasons not to build on them. The last page measures memory with process.memoryUsage, heap snapshots and --expose-gc, the flag every probe in this chapter uses.

The pages

Page Level In one line Status
Reachability 201 Node frees what the program can no longer reach from its roots, cycles included: two objects that point at each other survive gc() while one outside reference remains, and a WeakRef to one reads undefined after the next gc() once it is gone. stub
Memory leaks 301 In V8, closures made in one call share a scope object, so a callback that uses nothing keeps a million-element array alive because a sibling closure mentions it; and Node warns at the eleventh listener on one event with MaxListenersExceededWarning. stub
WeakRef and FinalizationRegistry 301 A FinalizationRegistry callback is not a destructor: drop the object and let the program end, and the callback never runs; even after a forced gc() it runs later, in a task of its own, after the program's last line. stub
Measuring memory 301 heapUsed counts only the JavaScript heap: allocating a 64 MiB Buffer changes it by under 1 MiB while arrayBuffers grows by 64 MiB, and globalThis.gc is undefined unless Node starts with --expose-gc. stub

Boundaries

WeakMap and WeakSet are covered with the other collections, and Buffer memory with the Node runtime.