DEV Community

Cover image for Part 1 - Memory Management in JavaScript: 10+ Data Structures & Garbage Collection Techniques | JS Deep Dive
navvan
navvan

Posted on

Part 1 - Memory Management in JavaScript: 10+ Data Structures & Garbage Collection Techniques | JS Deep Dive

Visit our official platform: Navvan
Follow us on: LinkedIn | YouTube

Memory management is a fundamental aspect of programming that varies significantly between languages. Low-level languages like C require manual memory management through functions like malloc() and free(), whereas high-level languages like JavaScript automate this process through garbage collection.

This article delves into the intricacies of memory management in JavaScript, exploring its lifecycle, allocation strategies, and the role of garbage collection.

Understanding the Memory Lifecycle

Every programming journey begins with understanding the memory lifecycle. Whether you're coding in C or JavaScript, the process is remarkably consistent:

  1. Allocate: Request the memory you need.
  2. Use: Utilize the allocated memory (read/write).
  3. Release: Free the memory once it's no longer required.

While the first and third steps are explicit in low-level languages, JavaScript handles the first and last steps implicitly, thanks to its automatic memory management features.

Allocation in JavaScript

JavaScript elegantly manages memory allocation to simplify development. When you declare values, it automatically allocates memory:

const n = 123; // Allocates memory for a number
const s = "azerty"; // Allocates memory for a string

const o = {
  a: 1,
  b: null,
}; // Allocates memory for an object and its contents

const a = [1, null, "abra"]; // Allocates memory for an array and its elements

function f(a) {
  return a + 2; // Allocates a function
}
document.getElementById('myButton').addEventListener('click', () => {
  document.body.style.backgroundColor = 'blue';
}, false);
Enter fullscreen mode Exit fullscreen mode

Releasing Unneeded Memory

Releasing memory when it's no longer needed is crucial. High-level languages like JavaScript employ garbage collection (GC) to automate this process. GC monitors memory allocation and decides when memory can be safely reclaimed.

Garbage Collection Explained

Garbage collection is a sophisticated technique used to manage memory. It works by identifying objects that are no longer accessible (i.e., not referenced by any live variables) and deallocating their memory. Two primary GC algorithms used are reference counting and mark-and-sweep.

Reference Counting

Reference counting tracks the number of references to an object. If an object's reference count drops to zero, it's considered safe to deallocate its memory. However, this method struggles with circular references, leading to memory leaks.

let primary = {
  key1: {
    key1_nested_key1: 2,
  },
};

let secondary = primary; // The 'secondary' variable is the second reference to the object.

primary = 1; // The object now has zero references and can be garbage-collected.

let tertiary = secondary.key1; // Reference to the 'key1' property of the object.
secondary = "Nikhil"; // The object has zero references now. It can be garbage collected.
tertiary = null; // The 'key1' property has zero references now. It can be garbage collected.
Enter fullscreen mode Exit fullscreen mode

Mark-and-Sweep

Mark-and-sweep is a more advanced garbage collection strategy. It starts from a set of root objects (e.g., global variables) and marks all objects reachable from these roots. Afterwards, it sweeps away all unmarked objects, deallocating their memory. This method efficiently handles circular references, making it the preferred choice for modern JavaScript engines.

Configuring Engine's Memory Model

JavaScript engines allow developers to configure memory settings, such as increasing the maximum heap size or exposing the garbage collector for debugging purposes. For instance, Node.js offers flags to adjust memory limits and enable garbage collector inspection:

node --max-old-space-size=4096 myScript.js
node --expose-gc --inspect myScript.js
Enter fullscreen mode Exit fullscreen mode

Advanced Data Structures for Memory Management

JavaScript introduces data structures like WeakMap and WeakSet, designed to aid in memory management by allowing objects to be garbage collected even if they're referenced within these structures. These structures hold weak references, meaning they don't prevent the garbage collector from collecting the referenced objects.

WeakMap and WeakSet

WeakMap and WeakSet allow you to associate objects with other objects or track unique values without preventing those objects from being garbage collected.

const weakMap = new WeakMap();
const key1 = {};
weakMap.set(key1, { key1 });
// Now `key1` cannot be garbage collected because the value holds a reference to it.
Enter fullscreen mode Exit fullscreen mode

WeakRef and FinalizationRegistry

WeakRef and FinalizationRegistry offer deeper insights into the garbage collection process. WeakRef allows you to hold a weak reference to an object, enabling it to be garbage collected while still accessing its value. FinalizationRegistry lets you register callbacks to run when an object is about to be garbage collected, facilitating cleanup operations.

const cache = new Map();
const registry = new FinalizationRegistry((value) => {
  cache.delete(value);
});

function cached(fetcher) {
  return async (key) => {
    let value = cache.get(key);
    if (value !== undefined) {
      return value.deref();
    }
    value = await fetcher(key);
    cache.set(key, new WeakRef(value));
    registry.register(value, key);
    return value;
  };
}
const getImage = cached(async (url) => fetch(url).then(res => res.blob()));
Enter fullscreen mode Exit fullscreen mode

If you enjoyed this article, please make sure to Subscribe, Like, Comment, and Connect with us today! 🌐

Visit our official platform: Navvan
Follow us on: LinkedIn | YouTube

Top comments (0)