`n

Memory Leak Debugging - Complete Guide

Published: September 25, 2024 | Reading time: 20 minutes

Memory Leak Overview

Memory leaks cause performance degradation and application crashes:

Memory Leak Impact
# Memory Leak Impact
- Performance degradation
- Application crashes
- Browser slowdown
- Increased CPU usage
- Poor user experience
- System instability
- Resource exhaustion

Memory Leak Detection

Browser DevTools

Memory Leak Detection Tools
# Memory Leak Detection

# 1. Chrome DevTools Memory Tab
# Open DevTools > Memory tab
# Take heap snapshots before and after operations
# Compare snapshots to identify memory leaks

# 2. Performance Monitor
class MemoryMonitor {
    constructor() {
        this.metrics = {
            usedJSHeapSize: 0,
            totalJSHeapSize: 0,
            jsHeapSizeLimit: 0,
            measurements: []
        };
        
        this.startMonitoring();
    }
    
    startMonitoring() {
        if ('memory' in performance) {
            setInterval(() => {
                this.recordMemoryUsage();
            }, 1000);
        }
    }
    
    recordMemoryUsage() {
        const memory = performance.memory;
        const measurement = {
            timestamp: Date.now(),
            used: memory.usedJSHeapSize,
            total: memory.totalJSHeapSize,
            limit: memory.jsHeapSizeLimit,
            percentage: (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100
        };
        
        this.metrics.measurements.push(measurement);
        
        // Keep only last 100 measurements
        if (this.metrics.measurements.length > 100) {
            this.metrics.measurements.shift();
        }
        
        // Alert if memory usage is high
        if (measurement.percentage > 80) {
            console.warn('High memory usage detected:', measurement);
        }
    }
    
    getMemoryTrend() {
        const measurements = this.metrics.measurements;
        if (measurements.length < 2) return null;
        
        const first = measurements[0];
        const last = measurements[measurements.length - 1];
        
        return {
            trend: last.used - first.used,
            growthRate: (last.used - first.used) / measurements.length,
            isGrowing: last.used > first.used
        };
    }
    
    generateReport() {
        const trend = this.getMemoryTrend();
        const current = this.metrics.measurements[this.metrics.measurements.length - 1];
        
        return {
            current: current,
            trend: trend,
            measurements: this.metrics.measurements.length,
            averageUsage: this.metrics.measurements.reduce((sum, m) => sum + m.used, 0) / this.metrics.measurements.length
        };
    }
}

# 3. Memory Leak Detector
class MemoryLeakDetector {
    constructor() {
        this.baseline = null;
        this.threshold = 0.1; // 10% increase threshold
        this.checkInterval = 5000; // 5 seconds
        this.isMonitoring = false;
    }
    
    startMonitoring() {
        if (this.isMonitoring) return;
        
        this.isMonitoring = true;
        this.baseline = this.getCurrentMemoryUsage();
        
        setInterval(() => {
            this.checkForLeaks();
        }, this.checkInterval);
    }
    
    stopMonitoring() {
        this.isMonitoring = false;
    }
    
    getCurrentMemoryUsage() {
        if ('memory' in performance) {
            return performance.memory.usedJSHeapSize;
        }
        return 0;
    }
    
    checkForLeaks() {
        const current = this.getCurrentMemoryUsage();
        const increase = current - this.baseline;
        const percentageIncrease = increase / this.baseline;
        
        if (percentageIncrease > this.threshold) {
            this.reportLeak({
                baseline: this.baseline,
                current: current,
                increase: increase,
                percentageIncrease: percentageIncrease
            });
        }
    }
    
    reportLeak(leakInfo) {
        console.error('Memory leak detected:', leakInfo);
        
        // Send to monitoring service
        if (window.gtag) {
            gtag('event', 'memory_leak', {
                baseline: leakInfo.baseline,
                current: leakInfo.current,
                increase: leakInfo.increase,
                percentage: leakInfo.percentageIncrease
            });
        }
    }
}

# 4. Object Reference Tracker
class ReferenceTracker {
    constructor() {
        this.references = new Map();
        this.weakRefs = new WeakMap();
    }
    
    trackObject(obj, name) {
        const id = this.generateId();
        this.references.set(id, {
            object: obj,
            name: name,
            timestamp: Date.now(),
            stack: new Error().stack
        });
        
        return id;
    }
    
    trackWeakReference(obj, name) {
        this.weakRefs.set(obj, {
            name: name,
            timestamp: Date.now()
        });
    }
    
    generateId() {
        return Math.random().toString(36).substr(2, 9);
    }
    
    getActiveReferences() {
        const active = [];
        
        for (const [id, ref] of this.references) {
            if (ref.object) {
                active.push({
                    id: id,
                    name: ref.name,
                    timestamp: ref.timestamp,
                    age: Date.now() - ref.timestamp
                });
            }
        }
        
        return active;
    }
    
    cleanup() {
        const toDelete = [];
        
        for (const [id, ref] of this.references) {
            if (!ref.object) {
                toDelete.push(id);
            }
        }
        
        toDelete.forEach(id => this.references.delete(id));
    }
}

# 5. Event Listener Leak Detector
class EventListenerLeakDetector {
    constructor() {
        this.originalAddEventListener = EventTarget.prototype.addEventListener;
        this.originalRemoveEventListener = EventTarget.prototype.removeEventListener;
        this.listeners = new Map();
        
        this.instrumentEventListeners();
    }
    
    instrumentEventListeners() {
        const self = this;
        
        EventTarget.prototype.addEventListener = function(type, listener, options) {
            const id = self.generateListenerId();
            self.listeners.set(id, {
                element: this,
                type: type,
                listener: listener,
                timestamp: Date.now(),
                stack: new Error().stack
            });
            
            return self.originalAddEventListener.call(this, type, listener, options);
        };
        
        EventTarget.prototype.removeEventListener = function(type, listener, options) {
            // Find and remove from tracking
            for (const [id, info] of self.listeners) {
                if (info.element === this && info.type === type && info.listener === listener) {
                    self.listeners.delete(id);
                    break;
                }
            }
            
            return self.originalRemoveEventListener.call(this, type, listener, options);
        };
    }
    
    generateListenerId() {
        return Math.random().toString(36).substr(2, 9);
    }
    
    getActiveListeners() {
        return Array.from(this.listeners.values());
    }
    
    detectLeaks() {
        const leaks = [];
        const now = Date.now();
        
        for (const [id, info] of this.listeners) {
            const age = now - info.timestamp;
            if (age > 60000) { // 1 minute
                leaks.push({
                    id: id,
                    element: info.element,
                    type: info.type,
                    age: age,
                    stack: info.stack
                });
            }
        }
        
        return leaks;
    }
}

# 6. Memory Usage Profiler
class MemoryProfiler {
    constructor() {
        this.profiles = [];
        this.isProfiling = false;
    }
    
    startProfiling(name) {
        if (this.isProfiling) return;
        
        this.isProfiling = true;
        this.currentProfile = {
            name: name,
            startTime: Date.now(),
            startMemory: this.getCurrentMemoryUsage(),
            snapshots: []
        };
    }
    
    stopProfiling() {
        if (!this.isProfiling) return;
        
        this.currentProfile.endTime = Date.now();
        this.currentProfile.endMemory = this.getCurrentMemoryUsage();
        this.currentProfile.duration = this.currentProfile.endTime - this.currentProfile.startTime;
        this.currentProfile.memoryDelta = this.currentProfile.endMemory - this.currentProfile.startMemory;
        
        this.profiles.push(this.currentProfile);
        this.isProfiling = false;
        
        return this.currentProfile;
    }
    
    takeSnapshot(label) {
        if (!this.isProfiling) return;
        
        const snapshot = {
            label: label,
            timestamp: Date.now(),
            memory: this.getCurrentMemoryUsage()
        };
        
        this.currentProfile.snapshots.push(snapshot);
    }
    
    getCurrentMemoryUsage() {
        if ('memory' in performance) {
            return performance.memory.usedJSHeapSize;
        }
        return 0;
    }
    
    getProfiles() {
        return this.profiles;
    }
    
    getProfileByName(name) {
        return this.profiles.find(profile => profile.name === name);
    }
}

Common Memory Leak Patterns

JavaScript Memory Leaks

Common Memory Leak Patterns
# Common Memory Leak Patterns

# 1. Event Listener Leaks
// Bad - Event listeners not removed
class BadComponent {
    constructor() {
        this.setupEventListeners();
    }
    
    setupEventListeners() {
        document.addEventListener('click', this.handleClick.bind(this));
        window.addEventListener('resize', this.handleResize.bind(this));
    }
    
    handleClick(event) {
        console.log('Clicked:', event.target);
    }
    
    handleResize(event) {
        console.log('Resized:', event.target);
    }
    
    // No cleanup method - memory leak!
}

// Good - Proper cleanup
class GoodComponent {
    constructor() {
        this.boundHandlers = {
            click: this.handleClick.bind(this),
            resize: this.handleResize.bind(this)
        };
        
        this.setupEventListeners();
    }
    
    setupEventListeners() {
        document.addEventListener('click', this.boundHandlers.click);
        window.addEventListener('resize', this.boundHandlers.resize);
    }
    
    handleClick(event) {
        console.log('Clicked:', event.target);
    }
    
    handleResize(event) {
        console.log('Resized:', event.target);
    }
    
    destroy() {
        document.removeEventListener('click', this.boundHandlers.click);
        window.removeEventListener('resize', this.boundHandlers.resize);
    }
}

# 2. Closure Leaks
// Bad - Closure holding references
function createLeakyFunction() {
    const largeArray = new Array(1000000).fill('data');
    
    return function() {
        // This closure holds reference to largeArray
        console.log('Function called');
    };
}

// Good - Avoid unnecessary closures
function createOptimizedFunction() {
    return function() {
        console.log('Function called');
    };
}

# 3. DOM Reference Leaks
// Bad - Holding DOM references
class DOMLeakExample {
    constructor() {
        this.elements = [];
        this.setupElements();
    }
    
    setupElements() {
        for (let i = 0; i < 1000; i++) {
            const element = document.createElement('div');
            element.textContent = `Element ${i}`;
            document.body.appendChild(element);
            
            // Storing DOM references - potential leak
            this.elements.push(element);
        }
    }
    
    // No cleanup method
}

// Good - Weak references or proper cleanup
class OptimizedDOMExample {
    constructor() {
        this.elementIds = [];
        this.setupElements();
    }
    
    setupElements() {
        for (let i = 0; i < 1000; i++) {
            const element = document.createElement('div');
            element.textContent = `Element ${i}`;
            element.id = `element-${i}`;
            document.body.appendChild(element);
            
            // Store only IDs, not DOM references
            this.elementIds.push(element.id);
        }
    }
    
    cleanup() {
        this.elementIds.forEach(id => {
            const element = document.getElementById(id);
            if (element) {
                element.remove();
            }
        });
        this.elementIds = [];
    }
}

# 4. Timer Leaks
// Bad - Timers not cleared
class TimerLeakExample {
    constructor() {
        this.startTimers();
    }
    
    startTimers() {
        this.intervalId = setInterval(() => {
            console.log('Interval running');
        }, 1000);
        
        this.timeoutId = setTimeout(() => {
            console.log('Timeout executed');
        }, 5000);
    }
    
    // No cleanup method - memory leak!
}

// Good - Proper timer cleanup
class OptimizedTimerExample {
    constructor() {
        this.timers = [];
        this.startTimers();
    }
    
    startTimers() {
        const intervalId = setInterval(() => {
            console.log('Interval running');
        }, 1000);
        
        const timeoutId = setTimeout(() => {
            console.log('Timeout executed');
        }, 5000);
        
        this.timers.push(intervalId, timeoutId);
    }
    
    cleanup() {
        this.timers.forEach(id => {
            clearInterval(id);
            clearTimeout(id);
        });
        this.timers = [];
    }
}

# 5. Observer Leaks
// Bad - Observers not disconnected
class ObserverLeakExample {
    constructor() {
        this.observer = new MutationObserver(this.handleMutations.bind(this));
        this.observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
    
    handleMutations(mutations) {
        mutations.forEach(mutation => {
            console.log('DOM mutated:', mutation);
        });
    }
    
    // No cleanup method - memory leak!
}

// Good - Proper observer cleanup
class OptimizedObserverExample {
    constructor() {
        this.observer = new MutationObserver(this.handleMutations.bind(this));
        this.observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
    
    handleMutations(mutations) {
        mutations.forEach(mutation => {
            console.log('DOM mutated:', mutation);
        });
    }
    
    cleanup() {
        this.observer.disconnect();
    }
}

# 6. Cache Leaks
// Bad - Unlimited cache growth
class CacheLeakExample {
    constructor() {
        this.cache = new Map();
    }
    
    set(key, value) {
        this.cache.set(key, value);
        // No size limit - potential memory leak
    }
    
    get(key) {
        return this.cache.get(key);
    }
}

// Good - Limited cache with LRU
class LRUCache {
    constructor(maxSize = 100) {
        this.maxSize = maxSize;
        this.cache = new Map();
    }
    
    set(key, value) {
        if (this.cache.has(key)) {
            this.cache.delete(key);
        } else if (this.cache.size >= this.maxSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
        
        this.cache.set(key, value);
    }
    
    get(key) {
        if (this.cache.has(key)) {
            const value = this.cache.get(key);
            this.cache.delete(key);
            this.cache.set(key, value);
            return value;
        }
        return undefined;
    }
}

# 7. React Memory Leaks
// Bad - No cleanup in useEffect
function LeakyComponent() {
    const [data, setData] = useState(null);
    
    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch('/api/data');
            const result = await response.json();
            setData(result);
        };
        
        fetchData();
        
        // No cleanup - potential memory leak
    }, []);
    
    return 
{data ? data.message : 'Loading...'}
; } // Good - Proper cleanup function OptimizedComponent() { const [data, setData] = useState(null); useEffect(() => { let isMounted = true; const fetchData = async () => { try { const response = await fetch('/api/data'); const result = await response.json(); if (isMounted) { setData(result); } } catch (error) { if (isMounted) { console.error('Fetch error:', error); } } }; fetchData(); return () => { isMounted = false; }; }, []); return
{data ? data.message : 'Loading...'}
; }

Memory Leak Prevention

Prevention Strategies

Memory Leak Prevention
# Memory Leak Prevention Strategies

# 1. Resource Management Pattern
class ResourceManager {
    constructor() {
        this.resources = new Set();
    }
    
    register(resource) {
        this.resources.add(resource);
        return resource;
    }
    
    unregister(resource) {
        this.resources.delete(resource);
    }
    
    cleanup() {
        this.resources.forEach(resource => {
            if (resource.cleanup) {
                resource.cleanup();
            }
        });
        this.resources.clear();
    }
}

# 2. WeakMap for Private Data
class WeakMapExample {
    constructor() {
        this.privateData = new WeakMap();
    }
    
    setPrivateData(obj, data) {
        this.privateData.set(obj, data);
    }
    
    getPrivateData(obj) {
        return this.privateData.get(obj);
    }
    
    // No need to manually clean up - WeakMap handles it
}

# 3. Event Listener Manager
class EventListenerManager {
    constructor() {
        this.listeners = new Map();
    }
    
    addEventListener(element, event, handler, options) {
        const key = `${element}-${event}`;
        const wrappedHandler = (e) => handler(e);
        
        element.addEventListener(event, wrappedHandler, options);
        
        if (!this.listeners.has(key)) {
            this.listeners.set(key, []);
        }
        this.listeners.get(key).push({ element, event, handler: wrappedHandler, options });
    }
    
    removeEventListener(element, event, handler) {
        const key = `${element}-${event}`;
        const listeners = this.listeners.get(key);
        
        if (listeners) {
            const index = listeners.findIndex(l => l.handler === handler);
            if (index !== -1) {
                element.removeEventListener(event, listeners[index].handler, listeners[index].options);
                listeners.splice(index, 1);
                
                if (listeners.length === 0) {
                    this.listeners.delete(key);
                }
            }
        }
    }
    
    removeAllListeners(element) {
        for (const [key, listeners] of this.listeners) {
            if (key.startsWith(element)) {
                listeners.forEach(({ element: el, event, handler, options }) => {
                    el.removeEventListener(event, handler, options);
                });
                this.listeners.delete(key);
            }
        }
    }
    
    cleanup() {
        for (const [key, listeners] of this.listeners) {
            listeners.forEach(({ element, event, handler, options }) => {
                element.removeEventListener(event, handler, options);
            });
        }
        this.listeners.clear();
    }
}

# 4. Timer Manager
class TimerManager {
    constructor() {
        this.timers = new Set();
    }
    
    setTimeout(callback, delay) {
        const id = setTimeout(() => {
            this.timers.delete(id);
            callback();
        }, delay);
        
        this.timers.add(id);
        return id;
    }
    
    setInterval(callback, interval) {
        const id = setInterval(() => {
            callback();
        }, interval);
        
        this.timers.add(id);
        return id;
    }
    
    clearTimeout(id) {
        clearTimeout(id);
        this.timers.delete(id);
    }
    
    clearInterval(id) {
        clearInterval(id);
        this.timers.delete(id);
    }
    
    cleanup() {
        this.timers.forEach(id => {
            clearTimeout(id);
            clearInterval(id);
        });
        this.timers.clear();
    }
}

# 5. Observer Manager
class ObserverManager {
    constructor() {
        this.observers = new Set();
    }
    
    createMutationObserver(callback, options) {
        const observer = new MutationObserver(callback);
        this.observers.add(observer);
        return observer;
    }
    
    createIntersectionObserver(callback, options) {
        const observer = new IntersectionObserver(callback, options);
        this.observers.add(observer);
        return observer;
    }
    
    disconnect(observer) {
        observer.disconnect();
        this.observers.delete(observer);
    }
    
    cleanup() {
        this.observers.forEach(observer => {
            observer.disconnect();
        });
        this.observers.clear();
    }
}

# 6. Memory-Aware Component Base Class
class MemoryAwareComponent {
    constructor() {
        this.resources = new ResourceManager();
        this.eventManager = new EventListenerManager();
        this.timerManager = new TimerManager();
        this.observerManager = new ObserverManager();
    }
    
    addEventListener(element, event, handler, options) {
        this.eventManager.addEventListener(element, event, handler, options);
    }
    
    setTimeout(callback, delay) {
        return this.timerManager.setTimeout(callback, delay);
    }
    
    setInterval(callback, interval) {
        return this.timerManager.setInterval(callback, interval);
    }
    
    createObserver(type, callback, options) {
        if (type === 'mutation') {
            return this.observerManager.createMutationObserver(callback, options);
        } else if (type === 'intersection') {
            return this.observerManager.createIntersectionObserver(callback, options);
        }
    }
    
    destroy() {
        this.resources.cleanup();
        this.eventManager.cleanup();
        this.timerManager.cleanup();
        this.observerManager.cleanup();
    }
}

# 7. Memory Leak Prevention Hooks
function useMemoryLeakPrevention() {
    const resources = useRef(new ResourceManager());
    const eventManager = useRef(new EventListenerManager());
    const timerManager = useRef(new TimerManager());
    const observerManager = useRef(new ObserverManager());
    
    useEffect(() => {
        return () => {
            resources.current.cleanup();
            eventManager.current.cleanup();
            timerManager.current.cleanup();
            observerManager.current.cleanup();
        };
    }, []);
    
    return {
        resources: resources.current,
        eventManager: eventManager.current,
        timerManager: timerManager.current,
        observerManager: observerManager.current
    };
}

# Usage in React component
function MyComponent() {
    const { eventManager, timerManager } = useMemoryLeakPrevention();
    
    useEffect(() => {
        const handleClick = (e) => console.log('Clicked:', e.target);
        eventManager.addEventListener(document, 'click', handleClick);
        
        const timerId = timerManager.setTimeout(() => {
            console.log('Timer executed');
        }, 5000);
        
        return () => {
            eventManager.removeEventListener(document, 'click', handleClick);
            timerManager.clearTimeout(timerId);
        };
    }, [eventManager, timerManager]);
    
    return 
My Component
; }

Memory Leak Debugging Tools

Debugging Tools and Techniques

Memory Leak Debugging Tools
# Memory Leak Debugging Tools

# 1. Chrome DevTools Memory Tab
# Steps to debug memory leaks:
# 1. Open DevTools > Memory tab
# 2. Select "Heap snapshot"
# 3. Take a snapshot before the operation
# 4. Perform the operation that causes the leak
# 5. Take another snapshot
# 6. Compare snapshots to identify retained objects

# 2. Performance Tab Analysis
# Steps to analyze memory leaks:
# 1. Open DevTools > Performance tab
# 2. Check "Memory" checkbox
# 3. Start recording
# 4. Perform operations that might cause leaks
# 5. Stop recording
# 6. Analyze memory usage graph

# 3. Custom Memory Debugger
class MemoryDebugger {
    constructor() {
        this.snapshots = [];
        this.isDebugging = false;
    }
    
    startDebugging() {
        this.isDebugging = true;
        this.takeSnapshot('start');
    }
    
    stopDebugging() {
        this.isDebugging = false;
        this.takeSnapshot('end');
        return this.generateReport();
    }
    
    takeSnapshot(label) {
        if (!this.isDebugging) return;
        
        const snapshot = {
            label: label,
            timestamp: Date.now(),
            memory: this.getMemoryInfo(),
            objects: this.getObjectCounts()
        };
        
        this.snapshots.push(snapshot);
    }
    
    getMemoryInfo() {
        if ('memory' in performance) {
            return {
                used: performance.memory.usedJSHeapSize,
                total: performance.memory.totalJSHeapSize,
                limit: performance.memory.jsHeapSizeLimit
            };
        }
        return null;
    }
    
    getObjectCounts() {
        // This is a simplified version
        // In reality, you'd need more sophisticated object counting
        return {
            nodes: document.querySelectorAll('*').length,
            listeners: this.countEventListeners(),
            timers: this.countTimers()
        };
    }
    
    countEventListeners() {
        // This is a simplified count
        // Real implementation would be more complex
        return document.querySelectorAll('*').length;
    }
    
    countTimers() {
        // This is a simplified count
        // Real implementation would track active timers
        return 0;
    }
    
    generateReport() {
        if (this.snapshots.length < 2) return null;
        
        const start = this.snapshots[0];
        const end = this.snapshots[this.snapshots.length - 1];
        
        return {
            duration: end.timestamp - start.timestamp,
            memoryDelta: end.memory.used - start.memory.used,
            objectDelta: {
                nodes: end.objects.nodes - start.objects.nodes,
                listeners: end.objects.listeners - start.objects.listeners,
                timers: end.objects.timers - start.objects.timers
            },
            snapshots: this.snapshots
        };
    }
}

# 4. Memory Leak Test Suite
class MemoryLeakTestSuite {
    constructor() {
        this.tests = [];
        this.results = [];
    }
    
    addTest(name, testFunction) {
        this.tests.push({ name, testFunction });
    }
    
    async runTests() {
        for (const test of this.tests) {
            const result = await this.runTest(test);
            this.results.push(result);
        }
        
        return this.results;
    }
    
    async runTest(test) {
        const debugger = new MemoryDebugger();
        debugger.startDebugging();
        
        try {
            await test.testFunction();
            
            // Wait for garbage collection
            await this.waitForGC();
            
            const report = debugger.stopDebugging();
            
            return {
                name: test.name,
                success: true,
                report: report
            };
        } catch (error) {
            debugger.stopDebugging();
            
            return {
                name: test.name,
                success: false,
                error: error.message
            };
        }
    }
    
    async waitForGC() {
        return new Promise(resolve => {
            setTimeout(resolve, 1000);
        });
    }
    
    generateTestReport() {
        const successful = this.results.filter(r => r.success);
        const failed = this.results.filter(r => !r.success);
        
        return {
            total: this.results.length,
            successful: successful.length,
            failed: failed.length,
            results: this.results
        };
    }
}

# 5. Memory Leak Detection in Production
class ProductionMemoryMonitor {
    constructor() {
        this.threshold = 0.8; // 80% memory usage threshold
        this.checkInterval = 30000; // 30 seconds
        this.isMonitoring = false;
    }
    
    startMonitoring() {
        if (this.isMonitoring) return;
        
        this.isMonitoring = true;
        this.monitor();
    }
    
    stopMonitoring() {
        this.isMonitoring = false;
    }
    
    monitor() {
        if (!this.isMonitoring) return;
        
        const memory = this.getMemoryUsage();
        if (memory > this.threshold) {
            this.handleHighMemoryUsage(memory);
        }
        
        setTimeout(() => this.monitor(), this.checkInterval);
    }
    
    getMemoryUsage() {
        if ('memory' in performance) {
            return performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit;
        }
        return 0;
    }
    
    handleHighMemoryUsage(usage) {
        console.warn('High memory usage detected:', usage);
        
        // Send to monitoring service
        this.sendToMonitoring({
            type: 'high_memory_usage',
            usage: usage,
            timestamp: Date.now()
        });
        
        // Attempt cleanup
        this.attemptCleanup();
    }
    
    sendToMonitoring(data) {
        if (window.gtag) {
            gtag('event', 'memory_warning', data);
        }
    }
    
    attemptCleanup() {
        // Force garbage collection if available
        if (window.gc) {
            window.gc();
        }
        
        // Clear caches
        if (window.caches) {
            caches.keys().then(names => {
                names.forEach(name => caches.delete(name));
            });
        }
    }
}

# 6. Memory Leak Prevention Checklist
class MemoryLeakPreventionChecklist {
    constructor() {
        this.checklist = [
            'Event listeners are properly removed',
            'Timers are cleared when no longer needed',
            'Observers are disconnected',
            'DOM references are nullified',
            'Closures don\'t hold unnecessary references',
            'Caches have size limits',
            'WeakMaps are used for private data',
            'Components have cleanup methods',
            'Memory usage is monitored',
            'Garbage collection is triggered when possible'
        ];
    }
    
    checkCode(code) {
        const results = [];
        
        for (const item of this.checklist) {
            results.push({
                item: item,
                passed: this.checkItem(code, item)
            });
        }
        
        return results;
    }
    
    checkItem(code, item) {
        // Simplified checking - in reality, you'd use AST parsing
        switch (item) {
            case 'Event listeners are properly removed':
                return code.includes('removeEventListener');
            case 'Timers are cleared when no longer needed':
                return code.includes('clearTimeout') || code.includes('clearInterval');
            case 'Observers are disconnected':
                return code.includes('disconnect()');
            default:
                return true;
        }
    }
}

Best Practices

Memory Leak Prevention Guidelines

Prevention Best Practices

  • Always clean up event listeners
  • Clear timers when done
  • Disconnect observers
  • Use WeakMap for private data
  • Implement cleanup methods
  • Monitor memory usage
  • Use resource managers

Common Leak Sources

  • Event listener accumulation
  • Timer accumulation
  • Observer accumulation
  • DOM reference retention
  • Closure reference cycles
  • Unlimited cache growth
  • Missing cleanup methods

Summary

Memory leak debugging involves several key components:

  • Detection: Browser DevTools, performance monitoring, custom detectors
  • Common Patterns: Event listeners, timers, observers, DOM references
  • Prevention: Resource management, cleanup patterns, weak references
  • Debugging Tools: DevTools, custom debuggers, test suites
  • Best Practices: Prevention guidelines, common pitfalls

Need More Help?

Struggling with memory leak debugging or need help optimizing your application's memory usage? Our performance experts can help you identify and fix memory leaks.

Get Memory Debugging Help