| name | memory-leak-detection |
| description | Detect and fix memory leaks using heap snapshots, memory profiling, and leak detection tools. Use when investigating memory growth, OOM errors, or optimizing memory usage. |
Memory Leak Detection
Overview
Identify and fix memory leaks to prevent out-of-memory crashes and optimize application performance.
When to Use
- Memory usage growing over time
- Out of memory (OOM) errors
- Performance degradation
- Container restarts
- High memory consumption
Implementation Examples
1. Node.js Heap Snapshots
import v8 from 'v8';
import fs from 'fs';
class MemoryProfiler {
takeSnapshot(filename: string): void {
const snapshot = v8.writeHeapSnapshot(filename);
console.log(`Heap snapshot saved to ${snapshot}`);
}
getMemoryUsage(): NodeJS.MemoryUsage {
return process.memoryUsage();
}
formatMemory(bytes: number): string {
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
}
printMemoryUsage(): void {
const usage = this.getMemoryUsage();
console.log('Memory Usage:');
console.log(` RSS: ${this.formatMemory(usage.rss)}`);
console.log(` Heap Total: ${this.formatMemory(usage.heapTotal)}`);
console.log(` Heap Used: ${this.formatMemory(usage.heapUsed)}`);
console.log(` External: ${this.formatMemory(usage.external)}`);
}
monitorMemory(interval: number = 5000): void {
setInterval(() => {
this.printMemoryUsage();
}, interval);
}
}
const profiler = new MemoryProfiler();
profiler.takeSnapshot('./heap-before.heapsnapshot');
await runApp();
profiler.takeSnapshot('./heap-after.heapsnapshot');
2. Memory Leak Detection Middleware
class LeakDetector {
private samples: number[] = [];
private maxSamples = 10;
private threshold = 1.5;
checkForLeak(): boolean {
const usage = process.memoryUsage();
this.samples.push(usage.heapUsed);
if (this.samples.length > this.maxSamples) {
this.samples.shift();
}
if (this.samples.length < this.maxSamples) {
return false;
}
const first = this.samples[0];
const last = this.samples[this.samples.length - 1];
const growth = last / first;
return growth > this.threshold;
}
startMonitoring(: = ): {
( {
(.()) {
.();
.(, ..(
));
}
}, interval);
}
}
detector = ();
detector.();
3. Common Memory Leak Patterns
class BadComponent {
constructor() {
window.addEventListener('resize', this.handleResize);
}
handleResize = () => {
}
}
class GoodComponent {
constructor() {
window.addEventListener('resize', this.handleResize);
}
handleResize = () => {
}
destroy() {
window.removeEventListener('resize', this.handleResize);
}
}
function badFunction() {
setInterval(() => {
doSomething();
}, 1000);
}
function goodFunction() {
const intervalId = setInterval(() => {
doSomething();
}, );
(intervalId);
}
() {
largeData = ().();
() {
.();
};
}
() {
needed = ;
() {
.(needed);
};
}
cache = [];
() {
cache.(item);
}
{
: [] = [];
maxSize = ;
() {
..(item);
(.. > .) {
..();
}
}
}
4. Python Memory Profiling
import tracemalloc
from typing import List, Tuple
class MemoryProfiler:
def __init__(self):
self.snapshots: List = []
def start(self):
"""Start tracking memory allocations."""
tracemalloc.start()
def take_snapshot(self):
"""Take a memory snapshot."""
snapshot = tracemalloc.take_snapshot()
self.snapshots.append(snapshot)
return snapshot
def compare_snapshots(
self,
snapshot1_idx: int,
snapshot2_idx: int,
top_n: int = 10
):
"""Compare two snapshots."""
snapshot1 = self.snapshots[snapshot1_idx]
snapshot2 = self.snapshots[snapshot2_idx]
stats = snapshot2.compare_to(snapshot1, 'lineno')
print(f"\nTop {top_n} memory differences:")
for stat in stats[:top_n]:
print(f"{stat.size_diff / 1024:.1f} KB: {stat.traceback}")
def get_top_allocations():
snapshot = .snapshots[snapshot_idx]
stats = snapshot.statistics()
()
stat stats[:top_n]:
()
():
tracemalloc.stop()
profiler = MemoryProfiler()
profiler.start()
profiler.take_snapshot()
data = [i i ()]
profiler.take_snapshot()
profiler.compare_snapshots(, )
profiler.stop()
5. WeakMap/WeakRef for Cache
class WeakCache<K extends object, V> {
private cache = new WeakMap<K, V>();
set(key: K, value: V): void {
this.cache.set(key, value);
}
get(key: K): V | undefined {
return this.cache.get(key);
}
has(key: K): boolean {
return this.cache.has(key);
}
delete(key: K): void {
this.cache.delete(key);
}
}
const cache = new WeakCache<object, string>();
let obj = { id: 1 };
cache.set(obj, 'data');
obj = null as any;
6. Memory Monitoring in Production
class MemoryMonitor {
private alerts: Array<(usage: NodeJS.MemoryUsage) => void> = [];
startMonitoring(options: {
interval?: number;
heapThreshold?: number;
rssThreshold?: number;
} = {}): void {
const {
interval = 60000,
heapThreshold = 0.9,
rssThreshold = 0.95
} = options;
setInterval(() => {
const usage = process.memoryUsage();
const heapUsedPercent = usage.heapUsed / usage.heapTotal;
if (heapUsedPercent > heapThreshold) {
console.warn(
`⚠️ High heap usage: ${(heapUsedPercent * 100).toFixed(2)}%`
);
this.alerts.forEach(fn => fn(usage));
if (global.gc) {
console.();
.();
}
}
}, interval);
}
(: ): {
..(callback);
}
}
monitor = ();
monitor.( {
.(, usage);
});
monitor.({
: ,
:
});
Best Practices
✅ DO
- Remove event listeners when done
- Clear timers and intervals
- Use WeakMap/WeakRef for caches
- Limit cache sizes
- Monitor memory in production
- Profile regularly
- Clean up after tests
❌ DON'T
- Create circular references
- Hold references to large objects unnecessarily
- Forget to clean up resources
- Ignore memory growth
- Skip WeakMap for object caches
Tools
- Node.js: Chrome DevTools, heapdump, memwatch-next
- Python: tracemalloc, memory_profiler, pympler
- Browsers: Chrome DevTools Memory Profiler
- Production: New Relic, DataDog APM
Resources