Skip to content

Blob/ArrayBuffer memory not reclaimed by GC after dereferencing #28741

@ReD-GuardiaN

Description

@ReD-GuardiaN

What version of Bun is running?

1.3.11

What platform is your computer?

Microsoft Windows NT 10.0.26200.0 x64

What steps can reproduce the bug?

  1. Fetch multiple URLs and store the resulting Blobs in an array
  2. Clear the array (blobs = []) to remove all references
  3. Call Bun.gc(true) multiple times, wait 8+ seconds
  4. Observe that RSS memory is not released

Single-file repro that works with both Bun and Node:

// Run: bun repro.js | node --expose-gc repro.js

const FETCH_COUNT = 50;
const isBun = !!globalThis.Bun;
const runtime = isBun ? `Bun ${Bun.version}` : `Node ${process.version}`;

let blobs = [];

const startRss = process.memoryUsage().rss / 1024 / 1024;
console.log(`${runtime} | Start memory: ${startRss.toFixed(1)}mb`);

for (let i = 0; i < FETCH_COUNT; i++) {
  const res = await fetch("https://www.w3schools.com/html/mov_bbb.mp4");
  blobs.push(await res.blob());
  if ((i + 1) % 10 === 0) {
    const rss = process.memoryUsage().rss / 1024 / 1024;
    console.log(`fetched ${i + 1}/${FETCH_COUNT}, memory: ${rss.toFixed(1)}mb`);
  }
}

const peakRss = process.memoryUsage().rss / 1024 / 1024;
console.log(`\nPeak memory: ${peakRss.toFixed(1)}mb (${blobs.length} blobs)`);

blobs = [];
if (isBun) Bun.gc(true);
if (global.gc) global.gc();
await new Promise((r) => setTimeout(r, 3000));
if (isBun) Bun.gc(true);
if (global.gc) global.gc();
await new Promise((r) => setTimeout(r, 5000));
if (isBun) Bun.gc(true);
if (global.gc) global.gc();

const finalRss = process.memoryUsage().rss / 1024 / 1024;
const released = peakRss - finalRss;
const pct = ((released / (peakRss - startRss)) * 100).toFixed(1);

console.log(`\nAfter GC: ${finalRss.toFixed(1)}mb`);
console.log(`Released: ${released.toFixed(1)}mb (${pct}%)`);
console.log(parseFloat(pct) > 70 ? "\n✅ GC working" : "\n❌ Memory leak — GC did not free blobs");

What is the expected behavior?

Memory should be reclaimed after all Blob references are removed and Bun.gc(true) is called. Node.js correctly frees ~73% of allocated memory in the same scenario.

What do you see instead?

Bun releases 0% of memory. Full comparison on the same machine:

Bun 1.3.11 Node v25.6.0
Start 105.1 MB 56.9 MB
Peak 184.2 MB 125.3 MB
After GC 184.3 MB 75.3 MB
Released -0.2 MB (0%) 50.0 MB (73%)

Additional information

Related issues:

  • Memory Leak with Blob/ArrayBuffer and Bun's Garbage Collection #12941 — I originally reported this on Bun 1.1.21. It was closed as "won't fix" by @Jarred-Sumner who used a modified repro wrapping blobs in a function scope. However, explicitly clearing references (blobs = []) + forced GC should work identically to scope exit — that's basic GC behavior. The bug persists on 1.3.11.

  • Bun memory leak with simple fetch polling script #28427 — Same underlying issue (fetch memory leak), opened recently, also on 1.3.11. The automated bot (robobun) marked it as "unable to reproduce" but used a local HTTP server instead of real remote fetches, which doesn't trigger the same code path for Blob/body buffering.

This is a clean, minimal repro with no cache: "force-cache", no frameworks, no extra dependencies — just fetchblob() → dereference → GC. The issue is in how Bun's runtime handles Blob/ArrayBuffer backing stores during garbage collection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions