AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 18 — Memory Management — Allocation, Reachability, and the Garbage Collector

18 — Memory Management — Allocation, Reachability, and the Garbage Collector

August 13, 20266 min read
Download as Markdown

"The language handles it for me" was my memory model, which made leaks and freezes feel like mysteries. The idea that everything else hangs off: *memory in JavaScript has a three-stage lifecycle — allocate, use, release — and release is automatic, driven by one rule: the garbage collector frees anything that is no longer reachable from a root.* [1]

The framing that finally landed is the reachability view. Unlike C, where I'd manually malloc and free, JavaScript allocates when I create a value and frees it when nothing can reach it anymore. "Reachable" means: starting from a set of roots (global variables, the current call stack, held event listeners), can I follow references and arrive at this object? If yes, it stays. If no — if it's an orphaned cluster with no path back to a root — the collector sweeps it. Memory leaks are not the GC failing; they're me keeping a path alive that I meant to drop.

1. Allocate implicit, on creation 2. Use read / write during run 3. Release GC frees the unreachable reachability — what the collector actually checks root reachable unreachable → freed

The three stages

  • Allocate — happens implicitly the moment I create a value [1]. const obj = { … } reserves memory for the object. const arr = [1,2,3] reserves memory for the array. I never call an allocation function; the engine does it for me as values come into being.
  • Use — the read/write phase. The program reads properties, calls methods, mutates the object. This is just normal execution.
  • Release — when the object is no longer reachable, the garbage collector reclaims its memory automatically [1][2]. I never call a free function.

The part that gave me a false sense of security for years was stage 3: because release is automatic, I assumed I didn't need to think about it. The catch is that automatic doesn't mean magic — it means "driven by reachability," and reachability is something I influence with every reference I hold.

Reachability and the garbage collector

The garbage collector's model is reachability [2]. It periodically traces from the roots — global variables, the current call stack, registered event listeners, closures in active use — and marks everything it can reach. Anything unmarked is garbage, and its memory is freed.

The two collection strategies the engines use are mark-and-sweep (trace from roots, mark reachable, sweep the rest) and, for short-lived objects, generational collection (new objects get collected frequently; long-lived ones get checked less often, since most garbage is short-lived). I don't control which runs when; I just control what's reachable.

How leaks actually happen

The insight that made leaks make sense: a leak isn't the GC failing to free memory — it's me keeping a reference path alive that I intended to drop. The object is still reachable, just from somewhere I forgot about. The common patterns [3]:

  • Unremoved event listeners. A listener holds a reference to its handler (and anything the handler closes over). If the element it's attached to is removed from the DOM but the listener isn't detached, both stay alive.
  • Lingering closures. A closure keeps its captured variables alive. If I cache a closure in a long-lived structure and forget to clear it, everything it closes over stays.
  • Global accumulation. Pushing into a module-level array or map and never removing. The structure is reachable from a root, so everything in it is too.
  • Detached DOM nodes. Keeping a JS reference to an element I removed from the document. The element is unreachable from the DOM tree but reachable from my variable, so it lingers.

In every case the fix is the same: explicitly drop the reference (removeEventListener, delete, set to null, clear the structure). The GC isn't broken; I just didn't sever the path.

How I use this

The discipline is to think in terms of reference lifetimes, not "freeing." When I add an event listener, I plan the matching removeEventListener. When I cache something in a long-lived structure, I plan how it gets evicted (a Map with size limits, a WeakMap keyed by an object that itself has a natural lifetime). For caches keyed by objects that may disappear, WeakMap and WeakSet are the purpose-built tools — they hold their keys weakly, so the entry vanishes automatically when the key is collected, no manual cleanup needed. And when a memory issue is suspected, the browser DevTools heap snapshot is how I actually see what's holding on — I take two snapshots, find the delta, and look at the retainers. The reachability picture is what tells me what to look for in that snapshot: not "what's the GC missing," but "what reference path did I fail to cut."

References

[1] Mozilla, "Memory management in JavaScript," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management

[2] I. Kantor, "Garbage collection," The Modern JavaScript Tutorial, 2024. [Online]. Available: https://javascript.info/garbage-collection

[3] DebugBear, "Debugging JavaScript memory leaks," 2022. [Online]. Available: https://www.debugbear.com/blog/debugging-javascript-memory-leaks

[4] Medium / Coding Blocks, "Catching memory leaks with Chrome DevTools," 2021. [Online]. Available: https://medium.com/coding-blocks/catching-memory-leaks-with-chrome-devtools-57b03acb6bb9

Knowledge check · Question 1 of 5

In JavaScript, when is memory allocated?

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!