javascript

examples

examples.js
/**
 * ============================================================
 * 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');
Examples - JavaScript Tutorial | DeepML