Docs
README
6.5 Memory Management
Introduction
JavaScript automatically manages memory, but understanding how it works helps you write more efficient code and avoid memory leaks. This section covers memory allocation, garbage collection, and common pitfalls.
Memory Lifecycle
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MEMORY LIFECYCLE β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. ALLOCATE β
β βββ JavaScript allocates memory when you create β
β variables, objects, functions, etc. β
β β
β 2. USE β
β βββ Read and write to allocated memory β
β β
β 3. RELEASE β
β βββ Garbage collector frees memory that is β
β no longer needed β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Stack vs Heap Memory
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MEMORY MODEL β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ€
β STACK β HEAP β
βββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ€
β β β
β β’ Primitives β β’ Objects β
β β’ Function calls β β’ Arrays β
β β’ Fixed size β β’ Functions β
β β’ Fast access β β’ Dynamic size β
β β’ Auto cleanup β β’ GC cleanup β
β β β
β βββββββββββββββ β βββββββββββββββββββββββββββ β
β β num: 42 β β β { name: "Alice", ... } β β
β βββββββββββββββ€ β βββββββββββββββββββββββββββ€ β
β β str: "hi" ββββββββΌβββ [1, 2, 3, 4, 5] β β
β βββββββββββββββ€ β βββββββββββββββββββββββββββ€ β
β β ref: ββββββββΌββββββΌβββ function() { ... } β β
β βββββββββββββββ β βββββββββββββββββββββββββββ β
β β β
βββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββ
Primitives (Stack):
let a = 10; // Stored in stack
let b = a; // Copy of value in stack
b = 20;
console.log(a); // 10 (unchanged)
Objects (Heap):
let obj1 = { x: 10 }; // Object in heap, reference in stack
let obj2 = obj1; // Same reference copied
obj2.x = 20;
console.log(obj1.x); // 20 (same object!)
Garbage Collection
JavaScript uses garbage collection (GC) to automatically free unused memory.
Mark-and-Sweep Algorithm:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MARK AND SWEEP β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. MARK PHASE β
β β’ Start from "roots" (global, stack variables) β
β β’ Mark all reachable objects β
β β
β 2. SWEEP PHASE β
β β’ Free unmarked (unreachable) objects β
β β
β β
β ROOT β
β β β
β [A] βββ [B] βββ [C] [D] [E] β
β β β β β
β [F] [G] [H] β
β β
β Reachable: A, B, C, F, G (marked β) β
β Unreachable: D, E, H (swept away) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
What Makes Something "Reachable"?
- β’Global variables (always reachable)
- β’Currently executing function's local variables
- β’Closure variables
- β’Objects referenced by reachable objects
Memory Leaks
A memory leak occurs when memory that is no longer needed is not released.
Common Causes:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β COMMON MEMORY LEAKS β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. Accidental globals β
β 2. Forgotten timers/callbacks β
β 3. Detached DOM nodes β
β 4. Closures holding references β
β 5. Event listeners not removed β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Leak 1: Accidental Globals
// BAD: Creates global variable (no var/let/const)
function leak() {
accidentalGlobal = "I'm global!"; // Oops!
}
leak();
// accidentalGlobal stays in memory forever
// GOOD: Use strict mode
function noLeak() {
'use strict';
accidentalGlobal = 'Error!'; // ReferenceError!
}
Leak 2: Forgotten Timers
// BAD: Timer keeps running, keeps reference to data
function startPolling() {
const largeData = new Array(1000000).fill('data');
setInterval(() => {
console.log(largeData.length); // Keeps largeData alive!
}, 1000);
}
// GOOD: Clear timers when done
function startPollingGood() {
const largeData = new Array(1000000).fill('data');
const timerId = setInterval(() => {
console.log(largeData.length);
}, 1000);
// Later, when done:
// clearInterval(timerId);
return timerId; // Allow cleanup
}
Leak 3: Detached DOM Nodes
// BAD: Removed from DOM but still referenced
let detachedElement;
function createLeak() {
const element = document.createElement('div');
document.body.appendChild(element);
detachedElement = element; // Keep reference
document.body.removeChild(element); // Removed from DOM
// Element still in memory because detachedElement holds it!
}
// GOOD: Clear references
function createNoLeak() {
const element = document.createElement('div');
document.body.appendChild(element);
document.body.removeChild(element);
// No reference kept, element can be garbage collected
}
Leak 4: Closures
// POTENTIAL LEAK: Closure keeps reference to large object
function createClosure() {
const largeArray = new Array(1000000).fill('x');
return function () {
// This closure keeps largeArray in memory!
console.log(largeArray.length);
};
}
const leakyFunction = createClosure();
// largeArray stays in memory as long as leakyFunction exists
// BETTER: Only keep what you need
function createBetterClosure() {
const largeArray = new Array(1000000).fill('x');
const length = largeArray.length; // Extract needed value
return function () {
console.log(length); // Only keeps the number
};
}
Leak 5: Event Listeners
// BAD: Listeners not removed
class LeakyComponent {
constructor() {
this.data = new Array(10000).fill('x');
window.addEventListener('resize', this.handleResize);
}
handleResize = () => {
console.log(this.data.length);
};
// Missing cleanup!
}
// GOOD: Remove listeners on cleanup
class GoodComponent {
constructor() {
this.data = new Array(10000).fill('x');
this.handleResize = this.handleResize.bind(this);
window.addEventListener('resize', this.handleResize);
}
handleResize() {
console.log(this.data.length);
}
destroy() {
window.removeEventListener('resize', this.handleResize);
}
}
Debugging Memory Issues
Chrome DevTools:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MEMORY DEBUGGING TOOLS β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. Memory Tab β
β β’ Heap snapshots β
β β’ Allocation timelines β
β β’ Allocation sampling β
β β
β 2. Performance Tab β
β β’ Memory graph over time β
β β’ Identify growing memory β
β β
β 3. Task Manager (Chrome) β
β β’ Shift+Esc β
β β’ See JS memory per tab β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Heap Snapshot Steps:
- β’Open DevTools β Memory tab
- β’Take "Heap snapshot"
- β’Perform actions that might leak
- β’Take another snapshot
- β’Compare snapshots to find growth
WeakMap and WeakSet
These collections allow garbage collection of their keys:
// Regular Map - prevents garbage collection
const cache = new Map();
let obj = { data: 'important' };
cache.set(obj, 'cached value');
obj = null; // Object still in memory (Map holds it)!
// WeakMap - allows garbage collection
const weakCache = new WeakMap();
let obj2 = { data: 'important' };
weakCache.set(obj2, 'cached value');
obj2 = null; // Object can be garbage collected!
When to Use:
// Good use case: Associating data with DOM elements
const elementData = new WeakMap();
function processElement(element) {
if (!elementData.has(element)) {
elementData.set(element, { processed: true });
}
return elementData.get(element);
}
// When element is removed from DOM and all references cleared,
// the associated data is also garbage collected
Memory Optimization Patterns
1. Object Pooling
// Instead of creating/destroying many objects:
class BulletPool {
constructor(size) {
this.pool = Array.from({ length: size }, () => ({
x: 0,
y: 0,
active: false,
}));
}
acquire() {
const bullet = this.pool.find((b) => !b.active);
if (bullet) bullet.active = true;
return bullet;
}
release(bullet) {
bullet.active = false;
bullet.x = 0;
bullet.y = 0;
}
}
2. Lazy Loading
// Don't load everything upfront
let heavyModule = null;
async function getHeavyModule() {
if (!heavyModule) {
heavyModule = await import('./heavyModule.js');
}
return heavyModule;
}
3. Chunked Processing
// Process large arrays in chunks to avoid memory spikes
async function processLargeArray(items, chunkSize = 1000) {
const results = [];
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
const processed = await processChunk(chunk);
results.push(...processed);
// Allow GC between chunks
await new Promise((r) => setTimeout(r, 0));
}
return results;
}
Summary
| Concept | Description |
|---|---|
| Stack | Fast, fixed-size memory for primitives |
| Heap | Dynamic memory for objects |
| Garbage Collection | Automatic memory cleanup |
| Mark and Sweep | GC algorithm - marks reachable, sweeps rest |
| Memory Leak | Memory not released when no longer needed |
| WeakMap/WeakSet | Collections that allow GC of keys |
| Object Pooling | Reuse objects instead of creating new ones |
Best Practices
- β’Use
constandlet- Avoid accidental globals - β’Clear timers and intervals -
clearTimeout,clearInterval - β’Remove event listeners - Especially on dynamic elements
- β’Avoid large closures - Only capture what's needed
- β’Nullify references - Set to
nullwhen done with large objects - β’Use WeakMap/WeakSet - For metadata attached to objects
- β’Profile regularly - Use DevTools Memory tab
Module 6 Complete!
You now understand:
- β’Execution contexts and how code runs
- β’The call stack and function execution
- β’Hoisting behavior for different declarations
- β’The event loop and async execution
- β’Memory management and garbage collection
These concepts are fundamental to writing efficient, bug-free JavaScript!