How Garbage Collection Works in JavaScript?

How Garbage Collection Works in JavaScript?

In JavaScript, garbage collection (GC) is the process of automatically reclaiming memory that is no longer in use, so developers don’t have to manually allocate and deallocate memory (as in languages like C or C++). The JavaScript engine (e.g., V8 in Chrome and Node.js) handles garbage collection in the background, ensuring efficient memory management.


Key Concepts in Garbage Collection

1. Reachability

  • Reachable objects are those that can be accessed or used in the program.

  • If an object is reachable, it is not garbage and won't be collected.

  • If an object becomes unreachable (i.e., no reference points to it), it is considered garbage and eligible for collection.

2. Root

  • The global execution context serves as the root of all objects.

  • Examples of roots:

    • Global variables (window object in browsers or global in Node.js).

    • The call stack (local variables and function parameters).

    • References in the DOM tree.

3. References

  • Objects are reachable as long as there are references to them from the root or other objects.

  • When an object has no references, it becomes unreachable and can be garbage collected.


How Garbage Collection Works

JavaScript engines use different algorithms for garbage collection. The most common approach is mark-and-sweep.


Mark-and-Sweep Algorithm

  1. Mark Phase:

    • The garbage collector starts with the root.

    • It marks all objects that are reachable directly or indirectly from the root.

  2. Sweep Phase:

    • It checks the memory for unmarked (unreachable) objects.

    • Unmarked objects are considered garbage and are removed from memory.


Illustration of Mark-and-Sweep

let obj = { name: "Alice" }; // The object is reachable (referenced by 'obj').

obj = null; // The object is now unreachable (no references).
// Garbage collector will eventually reclaim this memory.

Garbage Collection in Practice

1. When Objects Become Unreachable

  • Variable reassignment:

      let user = { name: "John" }; // 'user' references the object
      user = null; // Object becomes unreachable
    
  • Local scope:

      function greet() {
        let message = "Hello, World!";
        console.log(message); // 'message' is in use during the function execution
      }
      greet(); // After execution, 'message' becomes unreachable
    
  • Circular references: Even if two objects reference each other but are unreachable from the root, they are considered garbage:

      let obj1 = {};
      let obj2 = {};
      obj1.ref = obj2;
      obj2.ref = obj1;
    
      obj1 = null;
      obj2 = null; // Both objects are now unreachable
    

Optimizations in Modern JavaScript Engines

  1. Generational Garbage Collection:

    • Memory is divided into young generation (short-lived objects) and old generation (long-lived objects).

    • Young objects are collected more frequently (e.g., temporary variables).

  2. Incremental Garbage Collection:

    • Instead of pausing the entire program to clean memory, the garbage collector works in smaller chunks to avoid performance bottlenecks.
  3. Idle-Time Garbage Collection:

    • Garbage collection runs during idle times to minimize impact on program execution.

Garbage Collection Gotchas

1. Memory Leaks

A memory leak occurs when memory that is no longer needed cannot be reclaimed by the garbage collector due to lingering references.

  • Global Variables: If global variables are not cleaned up, they remain reachable:

      var data = {}; // Global variables persist until explicitly removed
    
  • Event Listeners: Forgetting to remove unused listeners keeps objects in memory:

      const button = document.getElementById("btn");
      function handleClick() {
        console.log("Button clicked!");
      }
      button.addEventListener("click", handleClick);
      // Forgetting to remove the listener keeps 'handleClick' and 'button' in memory.
    
  • Closures: Closures that reference variables in outer scopes can prevent garbage collection:

      function outer() {
        let largeArray = new Array(1000000); // Large object
        return function inner() {
          console.log(largeArray); // 'largeArray' is retained due to the closure
        };
      }
      const func = outer();
    

2. Circular References

While modern garbage collectors can handle circular references, older engines could struggle:

let objA = {};
let objB = {};
objA.link = objB;
objB.link = objA;

// Circular reference, but unreachable from the root
objA = null;
objB = null;

How to Minimize Memory Leaks

  1. Avoid Global Variables:

    • Use local variables or const/let instead of global var.
  2. Detach Event Listeners:

    • Always clean up event listeners when they’re no longer needed:

        button.removeEventListener("click", handleClick);
      
  3. Use WeakMap/WeakSet for Weak References:

    • These data structures do not prevent garbage collection for their values.

        const weakMap = new WeakMap();
        let obj = {};
        weakMap.set(obj, "value");
        obj = null; // Object is garbage collected
      
  4. Break References in Closures:

    • Ensure objects in closures are explicitly set to null when no longer needed.

Conclusion

JavaScript’s garbage collection is an automatic process managed by the engine, primarily using the mark-and-sweep algorithm to free up memory occupied by unreachable objects. While this simplifies memory management, developers should be mindful of potential memory leaks caused by global variables, event listeners, or closures. Understanding and applying best practices ensures efficient memory usage and prevents performance issues.

Did you find this article valuable?

Support Revive Coding by becoming a sponsor. Any amount is appreciated!