SpiderMonkey's Generational and Incremental GC Strategy

This article provides a comprehensive overview of the memory management techniques used by SpiderMonkey, the JavaScript engine powering the Firefox web browser. It explains how SpiderMonkey utilizes a combination of generational and incremental garbage collection (GC) strategies to reclaim memory efficiently. By dividing objects by age and breaking collection cycles into smaller pieces, Firefox minimizes pause times, prevents browser freezing, and maintains a smooth user experience.

The Core Mechanism: Mark-and-Sweep

At its foundation, SpiderMonkey uses a mark-and-sweep garbage collector. The collector starts from known "roots" (such as global variables and the current call stack) and traverses all reachable objects, marking them as alive. Once the marking phase is complete, the collector sweeps through memory, freeing up the space occupied by unmarked, unreachable objects.

In a naive implementation, this process halts all JavaScript execution—known as a "stop-the-world" pause. To prevent these pauses from disrupting webpage rendering and user interactions, SpiderMonkey enhances the basic mark-and-sweep algorithm with generational and incremental strategies.

Generational Garbage Collection

Generational collection is based on the "weak generational hypothesis," which observes that most objects in a program have a very short lifespan. SpiderMonkey exploits this by dividing the memory heap into two distinct areas: the Nursery and the Tenured heap.

The Nursery (Young Generation)

Almost all newly allocated JavaScript objects are placed in a small memory space called the nursery. Because most of these objects quickly become unreachable, SpiderMonkey performs frequent "Minor GCs" exclusively on the nursery. Since the nursery is small, scanning it takes only a fraction of a millisecond.

The Tenured Heap (Old Generation)

Objects that survive a Minor GC in the nursery are moved, or "promoted," to the tenured heap. This space holds long-lived objects. Because the tenured heap is much larger, scanning it takes significantly more time. Collecting the tenured heap is called a "Major GC," and it occurs much less frequently than a Minor GC.

Whole-Cell Store Buffer

For generational collection to work, SpiderMonkey must know if an old object in the tenured heap points to a new object in the nursery. Otherwise, a Minor GC might accidentally delete a nursery object that is still needed. SpiderMonkey uses a "store buffer" to record whenever a tenured object is updated to reference a nursery object, treating those specific references as additional roots during a Minor GC.

Incremental Garbage Collection

While generational collection reduces the frequency of Major GCs, the tenured heap eventually fills up and requires a full collection. Doing a Major GC all at once would pause the browser for hundreds of milliseconds, causing visible stuttering. SpiderMonkey solves this with incremental garbage collection.

Time Slicing

Instead of performing a Major GC in one continuous operation, SpiderMonkey breaks the marking phase into small, discrete time slices—typically lasting just a few milliseconds each. The engine runs a GC slice, pauses the collector to allow JavaScript and browser rendering to execute, and then resumes the GC in a later slice. This interleaving ensures the browser remains responsive to user input.

Write Barriers

Breaking up the marking phase introduces a major challenge: while the garbage collector is paused between slices, the running JavaScript code can change the object graph. The program might take an unvisited object and attach it to an object the GC has already scanned, tricking the GC into never marking the new attachment and falsely deleting it later.

To prevent this, SpiderMonkey uses "write barriers." Whenever JavaScript modifies a property on an object during an incremental GC, the write barrier intercepts the change and notifies the collector. SpiderMonkey uses a "snapshot-at-the-beginning" approach, ensuring the collector re-scans modified references so no live objects are accidentally swept away.

Background Sweeping and Compaction

Once the incremental marking slices are finished, SpiderMonkey moves to the sweeping phase. Almost all sweeping is done on background threads, allowing JavaScript execution to continue uninterrupted on the main thread.

Finally, to combat memory fragmentation—where free memory is broken into tiny, unusable gaps between live objects—SpiderMonkey occasionally performs a compacting GC. This process relocates live tenured objects together at the end of a Major GC, freeing up large, contiguous blocks of memory to return to the operating system.