Bun Memory Leaks: Production Debugging & Fixes
Critical Context
Severity: Critical production outages - RSS can grow from 200MB to 2GB during normal operation
Frequency: Daily RSS leaks of 30-35MB in idle child processes
Root Cause: JavaScriptCore memory architecture differs fundamentally from Node.js V8 engine
Technical Specifications
Memory Leak Types
Native Memory Leaks (Most Critical)
- RSS grows while JavaScript heap remains stable
- Child processes: 35MB daily RSS growth even when idle
- File uploads: Express + Multer confirmed bug (#18608)
- IPC channels: Messages persist longer than Node.js equivalent
JavaScript Heap Leaks
- Object count explosion: >50,000 objects per type indicates problems
- Closure scope capture: Entire parent scopes retained unintentionally
- AbortController retention: Event listeners prevent garbage collection
Performance Thresholds
Metric | Warning | Critical | Impact |
---|---|---|---|
RSS Growth | 800MB | 1.2GB | Docker OOM kills container |
Object Count | 50,000 | 100,000 | GC performance degradation |
Child Process RSS | 600MB | 800MB | Process restart required |
Daily RSS Growth | 35MB | 50MB | Unstable long-running processes |
JavaScriptCore vs V8 Architecture
JavaScriptCore (Bun)
- Incremental mark-and-sweep garbage collection
- Unpredictable GC timing
- Memory fragmentation into "moats"
- Objects live longer than expected
- No generational GC
V8 (Node.js)
- Generational garbage collection
- Predictable memory patterns
- Extensive debugging toolchain
- Better memory compaction
Debugging Methodology
Step 1: Identify Leak Type
import { heapStats } from "bun:jsc";
function detectLeakType() {
const rss = process.memoryUsage().rss;
const heap = heapStats();
if (rss > 800_000_000 && heap.heapSize < 100_000_000) {
return "NATIVE_LEAK"; // RSS high, heap low = native memory leak
}
if (heap.objectCount > 500_000) {
return "HEAP_LEAK"; // Object count explosion
}
return "STABLE";
}
Step 2: Chrome DevTools Analysis
- Only debugging tool that works with Bun
- Node.js tools (clinic.js, 0x, heapdump) incompatible
- Use heap snapshot comparison for leak detection
Step 3: Object Type Analysis
const suspicious = Object.entries(heapStats().objectTypeCounts)
.filter(([type, count]) => count > 10000)
.sort((a, b) => b[1] - a[1]);
Critical Object Count Indicators:
- Promise: >50,000 = unresolved promise chains
- Function: >50,000 = uncleaned event listeners
- Array: continuous growth = unbounded arrays
Production Fixes
Native Memory Leak Mitigation
Child Process Management (Required)
class ManagedWorker {
constructor() {
this.restartThreshold = 600 * 1024 * 1024; // 600MB
}
async monitorAndRestart() {
setInterval(() => {
if (process.memoryUsage().rss > this.restartThreshold) {
this.restart(); // Nuclear option - only working solution
}
}, 30000);
}
}
File Upload Workaround (Express + Multer Bug)
// BROKEN: multer.memoryStorage() leaks memory
const upload = multer({ storage: multer.memoryStorage() });
// WORKING: Disk storage with cleanup
const storage = multer.diskStorage({
destination: '/tmp/uploads',
filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`)
});
const safeUpload = multer({ storage, limits: { fileSize: 10_000_000 } });
JavaScript Heap Leak Solutions
Closure Scope Optimization
// PROBLEMATIC: Captures entire parent scope
function createHandler() {
const largeData = loadHugeDataset();
return (item) => largeData.find(x => x.id === item.id);
}
// OPTIMIZED: Extract only needed data
function createHandlerSafe() {
const largeData = loadHugeDataset();
const lookup = new Map(largeData.map(x => [x.id, x]));
largeData.length = 0; // Clear original reference
return (item) => lookup.get(item.id);
}
AbortController Memory Management
// Use WeakRef to prevent retention
const contextRef = new WeakRef(largeContext);
controller.signal.addEventListener('abort', () => {
const context = contextRef.deref();
if (context) cleanup(context);
});
Monitoring Implementation
Production Memory Monitor
class ProductionMemoryMonitor {
constructor() {
this.thresholds = {
rssWarning: 800_000_000, // 800MB
rssCritical: 1_200_000_000, // 1.2GB
objectCountWarning: 500_000
};
}
handleCriticalMemory() {
console.error('🚨 CRITICAL: Initiating graceful restart');
// Create emergency heap snapshot
writeHeapSnapshot(`critical-${Date.now()}.heapsnapshot`);
setTimeout(() => process.exit(1), 5000);
}
}
Memory Testing Framework
function testMemoryConsumption(operation, iterations = 1000) {
const acceptable = {
rssPerOp: 1024, // 1KB per operation
heapPerOp: 512, // 512 bytes per operation
objectsPerOp: 5 // 5 objects per operation
};
// Test operation multiple times and measure growth
// Fail if growth exceeds acceptable thresholds
}
Resource Requirements
Development Time:
- Initial debugging: 8-48 hours for first memory leak
- Monitor setup: 4-8 hours implementation
- Testing framework: 2-4 hours per critical path
Operational Overhead:
- Memory monitoring: ~20MB additional RAM usage
- Heap snapshots: 100MB-1GB disk space per snapshot
- Process restarts: 2-5 second downtime per restart
Expertise Requirements:
- JavaScriptCore garbage collection understanding
- Chrome DevTools heap analysis proficiency
- Native memory debugging skills (significantly harder than V8)
Critical Warnings
Documentation Gaps:
- Official Bun docs omit memory leak warnings
- Express + Multer incompatibility not documented
- Child process RSS growth not mentioned in spawn documentation
Breaking Points:
- Docker OOM kill at configured memory limits
- UI becomes unresponsive at 1000+ spans in distributed tracing
- File upload APIs fail after ~100 uploads with memory storage
Known Issues Without Fixes:
- Child process RSS leaks (Issue #21560) - open for months
- Express + Multer memory storage (Issue #18608) - confirmed bug
- No timeline for JavaScriptCore memory management improvements
Implementation Decision Matrix
Scenario | Recommended Action | Difficulty | Success Rate |
---|---|---|---|
Child process leaks | Scheduled restarts | Low | 95% |
File upload leaks | Switch to disk storage | Medium | 90% |
Heap object growth | Closure optimization | High | 70% |
Native memory leaks | Process monitoring + restart | Low | 85% |
Alternatives Assessment
Staying with Node.js:
- Mature memory debugging toolchain
- Predictable V8 garbage collection
- No native memory leak issues
- Trade-off: Slower startup times, larger bundle sizes
Continuing with Bun:
- Faster execution and startup
- Better TypeScript support
- Active development community
- Trade-off: Memory leak debugging complexity, production instability
Migration Cost: 2-4 weeks for typical API server, 1-2 months for complex applications with extensive Bun-specific optimizations.
Useful Links for Further Investigation
Essential Memory Troubleshooting Resources
Link | Description |
---|---|
Debugging JavaScript Memory Leaks | Actually helpful. Covers heap snapshots and common leak sources. |
Bun Runtime Configuration | Memory settings that might help. |
Bun.serve() Production Guide | Skips the memory leak warnings, naturally. |
Memory (RSS) in Bun Spawned Child Process Grows Slowly | The main event. Child process RSS leak, open for months. |
Memory leak with file uploads | Express + Multer broken, confirmed. |
Bun Memory Usage Issues | All memory bugs in one depressing list. |
Chrome DevTools Memory Tab | Your only real option for heap analysis |
Memory Leak Reading Files in Bun - Stack Overflow | Has some workarounds that might work |
Bun Discord Server | Group therapy for memory problems. |
Bun GitHub Discussions | Technical help, less memeing. |
Bun Benchmarks | Check if your fixes work. |
Streaming APIs for Memory Efficiency | Better than loading everything into memory |
Child Process Management Strategies | Won't fix the leaks but might help contain them |
Bun Production Issues Tracker | See if your problem is known |
Stack Overflow Bun Tag | Sometimes has solutions that actually work |
Related Tools & Recommendations
Fix Redis "ERR max number of clients reached" - Solutions That Actually Work
When Redis starts rejecting connections, you need fixes that work in minutes, not hours
QuickNode - Blockchain Nodes So You Don't Have To
Runs 70+ blockchain nodes so you can focus on building instead of debugging why your Ethereum node crashed again
Get Alpaca Market Data Without the Connection Constantly Dying on You
WebSocket Streaming That Actually Works: Stop Polling APIs Like It's 2005
OpenAI Alternatives That Won't Bankrupt You
Bills getting expensive? Yeah, ours too. Here's what we ended up switching to and what broke along the way.
Migrate JavaScript to TypeScript Without Losing Your Mind
A battle-tested guide for teams migrating production JavaScript codebases to TypeScript
Docker Compose 2.39.2 and Buildx 0.27.0 Released with Major Updates
Latest versions bring improved multi-platform builds and security fixes for containerized applications
Google Vertex AI - Google's Answer to AWS SageMaker
Google's ML platform that combines their scattered AI services into one place. Expect higher bills than advertised but decent Gemini model access if you're alre
Google NotebookLM Goes Global: Video Overviews in 80+ Languages
Google's AI research tool just became usable for non-English speakers who've been waiting months for basic multilingual support
Figma Gets Lukewarm Wall Street Reception Despite AI Potential - August 25, 2025
Major investment banks issue neutral ratings citing $37.6B valuation concerns while acknowledging design platform's AI integration opportunities
MongoDB - Document Database That Actually Works
Explore MongoDB's document database model, understand its flexible schema benefits and pitfalls, and learn about the true costs of MongoDB Atlas. Includes FAQs
How to Actually Configure Cursor AI Custom Prompts Without Losing Your Mind
Stop fighting with Cursor's confusing configuration mess and get it working for your actual development needs in under 30 minutes.
Cloudflare AI Week 2025 - New Tools to Stop Employees from Leaking Data to ChatGPT
Cloudflare Built Shadow AI Detection Because Your Devs Keep Using Unauthorized AI Tools
APT - How Debian and Ubuntu Handle Software Installation
Master APT (Advanced Package Tool) for Debian & Ubuntu. Learn effective software installation, best practices, and troubleshoot common issues like 'Unable to lo
jQuery - The Library That Won't Die
Explore jQuery's enduring legacy, its impact on web development, and the key changes in jQuery 4.0. Understand its relevance for new projects in 2025.
AWS RDS Blue/Green Deployments - Zero-Downtime Database Updates
Explore Amazon RDS Blue/Green Deployments for zero-downtime database updates. Learn how it works, deployment steps, and answers to common FAQs about switchover
KrakenD Production Troubleshooting - Fix the 3AM Problems
When KrakenD breaks in production and you need solutions that actually work
Fix Kubernetes ImagePullBackOff Error - The Complete Battle-Tested Guide
From "Pod stuck in ImagePullBackOff" to "Problem solved in 90 seconds"
Fix Git Checkout Branch Switching Failures - Local Changes Overwritten
When Git checkout blocks your workflow because uncommitted changes are in the way - battle-tested solutions for urgent branch switching
YNAB API - Grab Your Budget Data Programmatically
REST API for accessing YNAB budget data - perfect for automation and custom apps
NVIDIA Earnings Become Crucial Test for AI Market Amid Tech Sector Decline - August 23, 2025
Wall Street focuses on NVIDIA's upcoming earnings as tech stocks waver and AI trade faces critical evaluation with analysts expecting 48% EPS growth
Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization