javascript
examples
examples.js⚡javascript
/**
* ============================================================
* 22.3 ADVANCED GARBAGE COLLECTION - EXAMPLES
* ============================================================
*
* Demonstrations of GC behavior, memory management patterns,
* and optimization techniques.
*/
/**
* ============================================================
* EXAMPLE 1: OBSERVING MEMORY USAGE
* ============================================================
*/
console.log('=== Example 1: Memory Usage Observation ===\n');
function formatBytes(bytes) {
return (bytes / 1024 / 1024).toFixed(2) + ' MB';
}
function logMemory(label) {
if (typeof process !== 'undefined' && process.memoryUsage) {
const usage = process.memoryUsage();
console.log(`${label}:`);
console.log(` Heap Used: ${formatBytes(usage.heapUsed)}`);
console.log(` Heap Total: ${formatBytes(usage.heapTotal)}`);
} else {
console.log(`${label}: memory API not available in this environment`);
}
}
logMemory('Initial state');
// Allocate some objects
let data = [];
for (let i = 0; i < 100000; i++) {
data.push({ x: i, y: i * 2, z: i * 3 });
}
logMemory('After allocating 100k objects');
// Clear reference
data = null;
// Force GC if available
if (global.gc) {
global.gc();
logMemory('After GC (forced)');
} else {
console.log('Note: Run with node --expose-gc for manual GC control');
}
console.log();
/**
* ============================================================
* EXAMPLE 2: YOUNG VS OLD GENERATION
* ============================================================
*/
console.log('=== Example 2: Young vs Old Generation ===\n');
console.log('Demonstrating generational behavior:\n');
// Short-lived objects (stay in young generation, quickly collected)
function createShortLivedObjects(count) {
for (let i = 0; i < count; i++) {
const temp = { value: i, data: new Array(100).fill(i) };
// temp is immediately eligible for GC when function returns
}
}
console.log('Creating short-lived objects...');
const startShort = performance.now();
for (let i = 0; i < 100; i++) {
createShortLivedObjects(10000);
}
console.log(`Time: ${(performance.now() - startShort).toFixed(2)}ms`);
console.log(
'These objects were likely collected in young generation (fast minor GC)\n'
);
// Long-lived objects (promoted to old generation)
const longLived = [];
console.log('Creating long-lived objects...');
for (let i = 0; i < 10000; i++) {
longLived.push({ value: i, data: new Array(10).fill(i) });
}
console.log(
'These objects will be promoted to old generation after surviving scavenges\n'
);
// Clear for next examples
longLived.length = 0;
console.log();
/**
* ============================================================
* EXAMPLE 3: MEMORY LEAK PATTERNS
* ============================================================
*/
console.log('=== Example 3: Memory Leak Patterns ===\n');
// Pattern 1: Growing cache (leak)
console.log('Pattern 1: Unbounded Cache');
const leakyCache = {};
let leakyCacheSize = 0;
function addToLeakyCache(key, value) {
leakyCache[key] = value;
leakyCacheSize++;
}
// Simulate cache growth
for (let i = 0; i < 1000; i++) {
addToLeakyCache(`key_${i}`, { data: new Array(100).fill(i) });
}
console.log(` Leaky cache size: ${leakyCacheSize} entries`);
console.log(' ⚠️ This cache grows forever - memory leak!\n');
// Pattern 2: Bounded cache (fixed)
console.log('Pattern 2: Bounded LRU Cache');
class SimpleLRUCache {
constructor(maxSize) {
this.maxSize = maxSize;
this.cache = new Map();
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
}
this.cache.set(key, value);
if (this.cache.size > this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
}
get(key) {
if (!this.cache.has(key)) return undefined;
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
get size() {
return this.cache.size;
}
}
const boundedCache = new SimpleLRUCache(100);
for (let i = 0; i < 1000; i++) {
boundedCache.set(`key_${i}`, { data: new Array(100).fill(i) });
}
console.log(` Bounded cache size: ${boundedCache.size} entries`);
console.log(' ✅ Cache stays bounded - no leak!\n');
// Pattern 3: Closure over large scope (leak)
console.log('Pattern 3: Closure Retaining Large Data');
function createLeakyHandler() {
const hugeData = new Array(100000).fill('x'.repeat(100));
return function handler(index) {
// This closure captures hugeData forever!
return hugeData.length; // Only need length, not data
};
}
const leakyHandler = createLeakyHandler();
console.log(' Leaky handler retains hugeData: ~10MB+ in closure');
console.log(' ⚠️ Full array kept alive by closure!\n');
// Pattern 3 Fixed
function createOptimizedHandler() {
const hugeData = new Array(100000).fill('x'.repeat(100));
const dataLength = hugeData.length; // Extract what we need
// hugeData can now be GC'd, only dataLength is captured
return function handler() {
return dataLength;
};
}
const optimizedHandler = createOptimizedHandler();
console.log(' Optimized handler only retains length: ~8 bytes');
console.log(' ✅ Large data can be garbage collected!\n');
/**
* ============================================================
* EXAMPLE 4: WEAKMAP FOR METADATA
* ============================================================
*/
console.log('=== Example 4: WeakMap for Object Metadata ===\n');
// ❌ BAD: Regular Map prevents GC
console.log('Using regular Map (prevents GC):');
const regularMetadata = new Map();
function setMetaBad(obj, meta) {
regularMetadata.set(obj, meta);
}
{
const tempObject = { name: 'temporary' };
setMetaBad(tempObject, { created: Date.now() });
console.log(' Object added to Map');
// tempObject goes out of scope, but can't be GC'd
// because regularMetadata still references it
}
console.log(` Map size after scope: ${regularMetadata.size}`);
console.log(" ⚠️ Object can't be garbage collected!\n");
// ✅ GOOD: WeakMap allows GC
console.log('Using WeakMap (allows GC):');
const weakMetadata = new WeakMap();
function setMetaGood(obj, meta) {
weakMetadata.set(obj, meta);
}
{
const tempObject = { name: 'temporary' };
setMetaGood(tempObject, { created: Date.now() });
console.log(' Object added to WeakMap');
// tempObject goes out of scope
// It CAN be GC'd because WeakMap holds weak reference
}
console.log(' ✅ Object can be garbage collected!');
console.log(' (WeakMap size not accessible - by design)\n');
/**
* ============================================================
* EXAMPLE 5: OBJECT POOLING
* ============================================================
*/
console.log('=== Example 5: Object Pooling ===\n');
// Object Pool Implementation
class ObjectPool {
constructor(factory, reset, initialSize = 10) {
this.factory = factory;
this.reset = reset;
this.pool = [];
// Pre-populate pool
for (let i = 0; i < initialSize; i++) {
this.pool.push(this.factory());
}
this.stats = { acquired: 0, released: 0, created: initialSize };
}
acquire() {
this.stats.acquired++;
if (this.pool.length > 0) {
return this.pool.pop();
}
this.stats.created++;
return this.factory();
}
release(obj) {
this.stats.released++;
this.reset(obj);
this.pool.push(obj);
}
getStats() {
return {
...this.stats,
poolSize: this.pool.length,
};
}
}
// Vector object pool
const vectorPool = new ObjectPool(
() => ({ x: 0, y: 0, z: 0 }), // factory
(v) => {
v.x = 0;
v.y = 0;
v.z = 0;
} // reset
);
console.log('Simulating operations without pool:');
const startNoPool = performance.now();
for (let i = 0; i < 100000; i++) {
const v = { x: i, y: i * 2, z: i * 3 }; // New object each time
const result = v.x + v.y + v.z;
}
console.log(` Time: ${(performance.now() - startNoPool).toFixed(2)}ms`);
console.log('\nSimulating operations with pool:');
const startWithPool = performance.now();
for (let i = 0; i < 100000; i++) {
const v = vectorPool.acquire();
v.x = i;
v.y = i * 2;
v.z = i * 3;
const result = v.x + v.y + v.z;
vectorPool.release(v);
}
console.log(` Time: ${(performance.now() - startWithPool).toFixed(2)}ms`);
console.log(` Pool stats:`, vectorPool.getStats());
console.log(' ✅ Fewer allocations means less GC pressure\n');
/**
* ============================================================
* EXAMPLE 6: EVENT LISTENER CLEANUP
* ============================================================
*/
console.log('=== Example 6: Event Listener Cleanup Pattern ===\n');
// Simulated EventEmitter for Node.js environment
class SimpleEventEmitter {
constructor() {
this.listeners = new Map();
}
on(event, handler) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(handler);
}
off(event, handler) {
if (this.listeners.has(event)) {
this.listeners.get(event).delete(handler);
}
}
emit(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach((handler) => handler(data));
}
}
listenerCount(event) {
return this.listeners.has(event) ? this.listeners.get(event).size : 0;
}
}
// ❌ BAD: Forgetting to remove listeners
console.log('Bad pattern - listeners not removed:');
const emitter1 = new SimpleEventEmitter();
function BadComponent() {
function handleData(data) {
// Process data
}
emitter1.on('data', handleData);
// Component "destroyed" but listener remains!
}
BadComponent();
BadComponent();
BadComponent();
console.log(` Listener count: ${emitter1.listenerCount('data')}`);
console.log(' ⚠️ Listeners accumulating!\n');
// ✅ GOOD: Cleanup pattern
console.log('Good pattern - proper cleanup:');
const emitter2 = new SimpleEventEmitter();
function GoodComponent() {
function handleData(data) {
// Process data
}
emitter2.on('data', handleData);
// Return cleanup function
return function cleanup() {
emitter2.off('data', handleData);
};
}
const cleanup1 = GoodComponent();
const cleanup2 = GoodComponent();
const cleanup3 = GoodComponent();
console.log(` Before cleanup - listeners: ${emitter2.listenerCount('data')}`);
cleanup1();
cleanup2();
cleanup3();
console.log(` After cleanup - listeners: ${emitter2.listenerCount('data')}`);
console.log(' ✅ Listeners properly removed!\n');
/**
* ============================================================
* EXAMPLE 7: CIRCULAR REFERENCES
* ============================================================
*/
console.log('=== Example 7: Circular References ===\n');
console.log('Modern V8 handles circular references correctly:\n');
// Creating circular references
function createCircular() {
const a = { name: 'A' };
const b = { name: 'B' };
const c = { name: 'C' };
// Create circular references
a.next = b;
b.next = c;
c.next = a; // Circle complete!
a.prev = c;
b.prev = a;
c.prev = b;
return { a, b, c };
}
{
const circular = createCircular();
console.log(' Created circular structure: A → B → C → A');
console.log(' Objects reference each other in a circle');
// When circular goes out of scope...
}
console.log(" After scope ends: all objects can be GC'd");
console.log(' ✅ V8 uses mark-sweep which handles cycles correctly\n');
// Note: Old reference-counting GCs couldn't handle this
console.log(' Note: Reference counting would fail here (each object');
console.log(' would have refcount > 0). V8 uses reachability instead.\n');
/**
* ============================================================
* EXAMPLE 8: ARRAY PRE-ALLOCATION
* ============================================================
*/
console.log('=== Example 8: Array Pre-allocation ===\n');
const SIZE = 100000;
// ❌ BAD: Growing array dynamically
console.log('Dynamic array growth:');
const startDynamic = performance.now();
const dynamicArray = [];
for (let i = 0; i < SIZE; i++) {
dynamicArray.push(i); // May trigger reallocation
}
console.log(` Time: ${(performance.now() - startDynamic).toFixed(2)}ms`);
console.log(' ⚠️ Multiple internal reallocations\n');
// ✅ GOOD: Pre-allocated array
console.log('Pre-allocated array:');
const startPrealloc = performance.now();
const preallocArray = new Array(SIZE);
for (let i = 0; i < SIZE; i++) {
preallocArray[i] = i;
}
console.log(` Time: ${(performance.now() - startPrealloc).toFixed(2)}ms`);
console.log(' ✅ Single allocation\n');
// Even better with typed arrays for numbers
console.log('Typed array (Int32Array):');
const startTyped = performance.now();
const typedArray = new Int32Array(SIZE);
for (let i = 0; i < SIZE; i++) {
typedArray[i] = i;
}
console.log(` Time: ${(performance.now() - startTyped).toFixed(2)}ms`);
console.log(' ✅ Contiguous memory, no object overhead\n');
/**
* ============================================================
* EXAMPLE 9: FINALIZATION REGISTRY
* ============================================================
*/
console.log('=== Example 9: FinalizationRegistry (ES2021) ===\n');
if (typeof FinalizationRegistry !== 'undefined') {
// FinalizationRegistry lets you know when objects are GC'd
const registry = new FinalizationRegistry((heldValue) => {
console.log(` Cleanup for: ${heldValue}`);
});
console.log('Creating objects and registering for cleanup notification:');
{
const obj1 = { name: 'Object 1' };
const obj2 = { name: 'Object 2' };
registry.register(obj1, 'Object 1 was collected');
registry.register(obj2, 'Object 2 was collected');
console.log(' Registered 2 objects');
}
console.log(' Objects out of scope - awaiting GC notification');
console.log(' (Notifications happen asynchronously after GC)\n');
// Note: We can't force GC or guarantee when notifications happen
} else {
console.log(' FinalizationRegistry not available in this environment\n');
}
/**
* ============================================================
* EXAMPLE 10: WEAKREF
* ============================================================
*/
console.log('=== Example 10: WeakRef (ES2021) ===\n');
if (typeof WeakRef !== 'undefined') {
console.log('WeakRef allows holding weak reference to objects:\n');
let strongRef = { name: 'target object', data: new Array(1000) };
const weakRef = new WeakRef(strongRef);
console.log(' Object created and weak reference established');
console.log(` WeakRef.deref():`, weakRef.deref()?.name);
// Clear strong reference
strongRef = null;
console.log(' Strong reference cleared');
console.log(
` WeakRef.deref() now:`,
weakRef.deref()?.name ?? 'undefined (may be collected)'
);
console.log('\n Use case: Caches where entries can be automatically');
console.log(' cleaned up when memory pressure increases.\n');
} else {
console.log(' WeakRef not available in this environment\n');
}
/**
* ============================================================
* EXAMPLE 11: MEMORY-EFFICIENT DATA STRUCTURES
* ============================================================
*/
console.log('=== Example 11: Memory-Efficient Data Structures ===\n');
// Compare memory usage of different approaches
const COUNT = 10000;
// Regular objects with separate arrays
console.log('Approach 1: Array of Objects');
const objectArray = [];
for (let i = 0; i < COUNT; i++) {
objectArray.push({ x: i, y: i * 2, z: i * 3 });
}
console.log(` ${COUNT} objects created`);
console.log(' Each object has overhead for hidden class pointer\n');
// Struct of Arrays pattern
console.log('Approach 2: Struct of Arrays');
const structOfArrays = {
x: new Float64Array(COUNT),
y: new Float64Array(COUNT),
z: new Float64Array(COUNT),
};
for (let i = 0; i < COUNT; i++) {
structOfArrays.x[i] = i;
structOfArrays.y[i] = i * 2;
structOfArrays.z[i] = i * 3;
}
console.log(` 3 typed arrays of ${COUNT} elements each`);
console.log(' Much more memory efficient, better cache locality\n');
// Benchmark access patterns
console.log('Access pattern comparison:');
const startObjects = performance.now();
let sumObj = 0;
for (let j = 0; j < 100; j++) {
for (let i = 0; i < COUNT; i++) {
sumObj += objectArray[i].x + objectArray[i].y + objectArray[i].z;
}
}
console.log(
` Array of Objects: ${(performance.now() - startObjects).toFixed(2)}ms`
);
const startStruct = performance.now();
let sumStruct = 0;
for (let j = 0; j < 100; j++) {
for (let i = 0; i < COUNT; i++) {
sumStruct +=
structOfArrays.x[i] + structOfArrays.y[i] + structOfArrays.z[i];
}
}
console.log(
` Struct of Arrays: ${(performance.now() - startStruct).toFixed(2)}ms`
);
console.log();
/**
* ============================================================
* EXAMPLE 12: GC-FRIENDLY CODING PATTERNS
* ============================================================
*/
console.log('=== Example 12: GC-Friendly Patterns Summary ===\n');
console.log('1. Reuse objects instead of creating new ones');
console.log(' const result = {}; // Reuse');
console.log(' result.x = computeX();');
console.log(' result.y = computeY();');
console.log('\n2. Avoid closures over large data');
console.log(' Extract only needed values before creating closure');
console.log('\n3. Use typed arrays for numeric data');
console.log(' new Float64Array(size) instead of new Array(size)');
console.log('\n4. Null out references when done');
console.log(' largeData = null; // Allow GC');
console.log('\n5. Use WeakMap/WeakSet for caches and metadata');
console.log(' const cache = new WeakMap();');
console.log('\n6. Implement bounded caches');
console.log(' Limit cache size with LRU eviction');
console.log('\n7. Clean up event listeners');
console.log(' Always removeEventListener when done');
console.log('\n8. Avoid creating objects in hot loops');
console.log(' Move object creation outside the loop');
console.log('\n9. Use object pools for frequently created/destroyed objects');
console.log(' Recycle objects instead of creating new ones');
console.log('\n10. Pre-allocate arrays when size is known');
console.log(' new Array(knownSize) or new TypedArray(knownSize)');
console.log('\n=== Examples Complete ===\n');