Streambert Memory Management and V8 Heap Limits: Optimizing Electron Renderer Memory

Streambert caps its Electron renderer V8 heap at 256 MiB using --max-old-space-size=256 and exposes global.gc() to manually trigger garbage collection after video playback sessions, preventing out-of-memory crashes on low-end hardware.

Streambert is an Electron-based streaming application designed to run smoothly on resource-constrained devices. By analyzing the truelockmc/streambert source code, we can see exactly how Streambert memory management v8 heap limits are implemented through explicit V8 flags, dual-process garbage collection triggers, and aggressive native cache eviction strategies.

Configuring V8 Heap Limits in the Main Process

Streambert’s main process (index.js) explicitly configures the renderer process V8 engine during BrowserWindow creation. This proactive configuration prevents the JavaScript heap from growing unbounded during extended streaming sessions.

Setting the Old Generation Cap

In index.js at lines 28–29, the webPreferences object passes V8 flags through the additionalArguments array:

webPreferences: {
  preload: path.join(__dirname, "preload.js"),
  contextIsolation: true,
  nodeIntegration: false,
  webviewTag: true,
  backgroundThrottling: true,
  spellcheck: false,
  // Caps the renderer's V8 heap + exposes gc() for manual GC hints after navigation
  additionalArguments: [
    "--js-flags=--max-old-space-size=256 --expose-gc",
  ],
},

The --max-old-space-size=256 flag restricts V8’s old generation heap to 256 MiB. This limit forces the garbage collector to run more frequently rather than allowing memory to accumulate indefinitely, which is critical for preventing renderer process crashes during heavy video playback.

Exposing the Garbage Collector

The same additionalArguments entry includes --expose-gc, which injects a global.gc() function into the JavaScript context. According to the source code at index.js, this exposed function is used to force synchronous garbage collection at strategic moments when the application knows large objects (like video buffers) have become unreachable.

Manual Memory Reclamation Workflows

Beyond automatic V8 garbage collection, Streambert implements explicit memory reclamation workflows that target both JavaScript-managed memory and native resources that V8 cannot see.

Post-Navigation Cleanup in the Main Process

When a player WebView stops, the main process triggers garbage collection immediately. At index.js line 357, the code checks for the exposed function and invokes it:

if (typeof global.gc === "function") global.gc();

This call runs a full collection on the main process’s heap, reclaiming memory from the closed player components before the UI renders the next screen.

Renderer-Side GC Invocation

To ensure the renderer process also releases memory, line 361 sends an explicit GC request into the renderer via executeJavaScript:

mw.webContents.executeJavaScript("if(typeof gc==='function') gc();");

This dual-process collection strategy ensures that both the main Electron process and the renderer WebContents free unused objects, minimizing the application's total memory footprint.

Player Session Cache Flushing

Native-level memory (HTTP caches, shader compilations) exists outside the V8 heap and requires explicit clearing. In index.js lines 49‑55, Streambert configures a persistent persist:player session that is aggressively cleared when playback ends:

// Clear cache and storage for the player session to free native memory
ps.clearCache().then(() => {
  ps.clearStorageData({
    storages: ["shadercache", "cachestorage"],
    quotas: ["temporary", "persistent"],
  });
});

The clearCache() and clearStorageData() calls free GPU shader caches and HTTP response caches that would otherwise persist across video sessions.

In-Memory Cache Management

Streambert also bounds application-level caches that could grow indefinitely during long usage sessions.

TMDB API Response Caching

The TMDB utility (src/utils/api.js) maintains an in-memory response cache to reduce API calls. At line 107, the code implements TTL-based eviction to prevent unbounded growth:

// Evict stale entries to prevent unbounded memory growth
if (Date.now() - entry.timestamp > MAX_AGE_MS) {
  delete cache[key];
}

Entries older than the configurable maximum age (default 10 minutes) are deleted, ensuring the cache remains small even during extended browsing.

Block Statistics Tracking

For runtime analytics, src/ipc/blockStats.js line 56 maintains a lightweight in-memory counter:

// Update alltime in-memory

This counter accumulates statistics only for the current application lifecycle and is automatically cleared on restart, avoiding permanent memory bloat in the V8 heap.

Complete Player Memory Cleanup Flow

The comprehensive cleanup sequence is implemented in index.js lines 329‑364 within the "Player memory cleanup" block. When a video stops, Streambert executes a three-step reclamation process:

  1. Destroy Tracked WebContents: Iterates through playerWcIds to mute and destroy each player WebView instance.
  2. Flush Native Caches: Calls clearCache() and clearStorageData() on the persistent player session.
  3. Trigger Dual GC: Runs global.gc() in the main process, then injects gc() execution into the renderer.

This coordinated approach fully reclaims memory allocated by the player WebView, including both V8-managed JavaScript objects and Electron’s native C++ resources.

Practical Implementation Examples

You can interact with or modify Streambert’s memory management in several ways.

Adjusting the Heap Limit at Build Time

For machines with more available RAM, increase the old-space size in index.js:

additionalArguments: [
  "--js-flags=--max-old-space-size=512 --expose-gc", // 512 MiB instead of 256
],

Manually Triggering GC from the Renderer

Any renderer process code can force collection:

if (typeof gc === "function") {
  gc(); // forces V8 to run a collection
}

Clearing the TMDB Cache On Demand

Import the utility to purge cached API responses immediately:

import { clearTmdbCache } from "./utils/api";

function onUserRequestRefresh() {
  clearTmdbCache(); // empties the cache immediately
}

Monitoring Heap Usage (Debug Builds)

Inspect current memory consumption from the main process:

const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`Heap used: ${used.toFixed(2)} MiB`);

Summary

  • V8 heap limits are enforced via --max-old-space-size=256 in index.js to cap old-generation memory at 256 MiB.
  • Manual garbage collection is enabled by --expose-gc and triggered in both main and renderer processes after video playback ends.
  • Native cache eviction clears HTTP and shader caches via clearCache() and clearStorageData() in the player session.
  • Application caches use TTL-based eviction (src/utils/api.js line 107) and lifecycle-bound counters (src/ipc/blockStats.js line 56) to prevent unbounded growth.
  • The complete cleanup workflow at index.js lines 329‑364 coordinates WebContents destruction, native cache flushing, and dual-process GC.

Frequently Asked Questions

Why does Streambert limit the V8 heap to 256 MiB?

The 256 MiB cap in index.js prevents the Electron renderer process from consuming all available system memory during video streaming, which keeps the UI responsive on low-end machines with limited RAM. This conservative limit forces more frequent garbage collection cycles rather than allowing memory to accumulate indefinitely.

How does Streambert clean up memory after a video stops?

When playback ends, the code at index.js lines 329‑364 executes a three-part cleanup: it destroys the player WebContents instances, clears the persistent session’s HTTP and shader caches via clearStorageData(), and triggers global.gc() in both the main process and renderer process to immediately reclaim unreachable objects.

What happens to the TMDB API cache when it grows too large?

The TMDB helper in src/utils/api.js line 107 implements time-to-live (TTL) eviction. When retrieving cached entries, the code checks if Date.now() - entry.timestamp exceeds MAX_AGE_MS (default 10 minutes) and deletes stale entries, ensuring the in-memory cache never grows beyond a bounded set of recent responses.

Can I increase the V8 heap limit if my machine has more RAM?

Yes. Edit the additionalArguments array in index.js at lines 28‑29 and modify the --max-old-space-size value (for example, to 512 for 512 MiB). This adjustment requires rebuilding the application, as V8 flags are set at process startup and cannot be changed at runtime.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →