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:
- Allocate: Request the memory you need.
- Use: Utilize the allocated memory (read/write).
- 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);
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.
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
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.
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()));
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