Bun uses JavaScriptCore instead of V8, and its garbage collector is weird as hell - like, genuinely unpredictable weird. I've been fighting these memory issues in production for what feels like forever, and the debugging tools are absolute garbage compared to Node.js memory debugging. Here's the stuff I wish someone had told me before I wasted months on this.
JavaScriptCore does this thing where... fuck, I don't even know how to explain it properly
It holds onto closures like a hoarder. V8 at least tries to optimize scopes, but JavaScriptCore? Nah, it keeps the entire parent scope alive. Found this out when our API server went from maybe 200MB to... shit, 8GB? Over a single weekend because we were creating handlers that referenced this massive config object. Took me like 2 weeks to figure out what the hell was happening.
The GC does whatever it wants. V8 at least has some predictable patterns, but JavaScriptCore just decides when it feels like cleaning up. Your memory can grow for hours - I've watched it climb to like 3GB before it bothered doing anything. Sometimes it never happens at all.
The debugging tools are garbage. This pisses me off more than anything. Chrome DevTools? Good luck with that. The object structure looks completely alien compared to V8 and nothing makes sense. I've wasted more time trying to decode heap snapshots than actually fixing leaks.
The memory leaks that will absolutely ruin your week (I'm still recovering from these)
1. File Processing That Never Releases Memory (this one almost made me quit programming)
If you're processing files, images, or any large data, Bun has this nasty habit of never releasing Blobs and ArrayBuffers. Issue #12941 documents this exact problem, and there are tons of related ArrayBuffer issues scattered across their issue tracker. The Blob API documentation conveniently doesn't mention any of these limitations. This shit will straight up eat your memory:
// This killed our image processing server
const processFiles = async (urls) => {
let blobs = [];
for (const url of urls) {
const response = await fetch(url);
const blob = await response.blob(); // Never gets freed
blobs.push(blob);
}
// None of this works:
blobs = null;
Bun.gc(true); // Useless
// Memory still climbing to 8GB
};
2. Next.js Builds That Eat All Your Memory (took down our entire deployment pipeline)
If you're using Bun with Next.js for static generation on anything bigger than like 50 pages, you're completely screwed. Issue #16339 documents this continuous memory growth during builds, and there are a bunch of similar Next.js problems from other poor bastards who tried this. The Next.js + Bun guide doesn't warn you about any of this bullshit. I literally sat there and watched our massive site go from maybe 400MB to... fuck, 6GB? Before it just crashed when it hit the container limit. Wasted my entire afternoon debugging this crap.
// This config won't help
module.exports = {
cacheHandler: require.resolve('./cache-handler.js'),
cacheMaxMemorySize: 0, // Doesn't work
}
// Build: 400MB -> 1GB -> 2GB -> 4GB -> CRASH
We had to split our build into chunks and restart the process every 1000 pages. Fun times.
Actually, speaking of fun times - trying to explain this to management was... well, "Hey, so our new super-fast runtime randomly eats 8GB of memory and crashes" didn't go over well.
3. Workers That Leave Memory Ghosts Behind
Bun's workers leak memory when they terminate. Multiple worker-related memory issues exist in the tracker. The Worker API documentation doesn't mention cleanup problems. Create 100 workers that do some processing and exit? You'll still see their memory usage hanging around like ghosts. The only fix is restarting your main process, which is pretty fucking useless in production.
How to Catch Memory Leaks Before They Kill You
Simple Memory Monitoring (Actually Works):
// This saved us when containers started dying
setInterval(() => {
const memMB = Math.round(process.memoryUsage().rss / 1024 / 1024);
if (memMB > 1500) {
console.error(`MEMORY CRITICAL: ${memMB}MB - shutting down gracefully`);
process.exit(1); // Better than OOMKill
}
}, 30000);
Heap Snapshots: Good luck with that. They're way harder to read than Node.js ones and Chrome DevTools barely makes sense of them.
Pro tip: Chrome DevTools can load these, but the object structure is different from V8. Compare this to Node.js heap profiling and clinic.js flame graphs - it's night and day. I spent hours trying to make sense of the snapshots before giving up and just adding more monitoring. The Bun profiling guide helps, but it's still confusing compared to V8 debugging tools.
Now that you understand why Bun's memory behavior is so unpredictable, let's look at how to actually debug these issues when they hit production.