Memory Management & Garbage Collection
20 min read
Garbage CollectionMark-and-SweepMemory LeaksWeakRefFinalizationRegistryDevTools MemoryClosures
How JavaScript Manages Memory
Memory Lifecycle
- Allocate: Memory is allocated when variables, objects, functions are created
- Use: Reading and writing to allocated memory
- Release: When memory is no longer needed (garbage collection)
Mark-and-Sweep Algorithm
Modern JS engines use Mark-and-Sweep:
- Start from "roots" (global scope, call stack)
- Mark all objects reachable from roots
- Sweep (free) all unmarked objects
Common Memory Leak Patterns
// 1. Forgotten Event Listeners
function setup() {
const button = document.getElementById("btn");
const heavyData = new Array(1000000).fill("x");
button.addEventListener("click", () => {
console.log(heavyData.length); // heavyData trapped in closure!
});
// ❌ Listener never removed = heavyData never freed
}
// ✅ Fix: Remove listener when done
const handler = () => console.log("clicked");
button.addEventListener("click", handler);
// Later:
button.removeEventListener("click", handler);// 2. Detached DOM Nodes
let detachedNodes = [];
function createList() {
const ul = document.createElement("ul");
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
ul.appendChild(li);
}
document.body.appendChild(ul);
detachedNodes.push(ul); // ❌ Reference kept after removal
document.body.removeChild(ul);
}
// ✅ Fix: Don't keep references to removed DOM nodes
// Set detachedNodes = null when no longer needed// 3. Closures Holding Large Data
function processData() {
const hugeArray = fetchHugeDataset(); // 100MB
return function getLength() {
return hugeArray.length; // Closure traps entire hugeArray!
};
}
// ✅ Fix: Extract only what you need
function processData() {
const hugeArray = fetchHugeDataset();
const length = hugeArray.length;
// hugeArray can now be GC'd
return function getLength() {
return length;
};
}WeakRef & FinalizationRegistry (ES2021)
// WeakRef: Hold a reference that doesn't prevent GC
let obj = { data: "important" };
const weakRef = new WeakRef(obj);
obj = null; // Now eligible for GC
// Later: might be undefined if GC ran
const deref = weakRef.deref();
if (deref) console.log(deref.data);
// FinalizationRegistry: Callback when object is GC'd
const registry = new FinalizationRegistry((value) => {
console.log(`${value} was garbage collected`);
});
registry.register(obj, "my-object");Debugging with Chrome DevTools
- Memory tab → Heap Snapshot: Compare snapshots to find growing objects
- Memory tab → Allocation Timeline: See when and where memory is allocated
- Performance tab: Look for growing JS Heap in the chart
- Detached Nodes: Filter heap snapshot by "Detached" to find DOM leaks