< Back to all hacks

#14 Low-Memory Mode (--max-old-space-size)

RAM
Problem
Gateway needs 218+ MB heap, OOM kill with Android + proot overhead.
Solution
--max-old-space-size=384 initially. Kill Google services before launch (but NOT GMS!).
Lesson
V8 defaults to half of system RAM for heap. On constrained devices, explicit limits prevent OOM spirals.

Context

Node.js V8 engine defaults to using roughly half of available system RAM for its heap. On a server with 8 GB, this means a 4 GB heap limit — perfectly reasonable. On the Moto E2 with 1 GB total (and ~400 MB available after Android), V8 would try to allocate ~500 MB and get immediately OOM-killed by the Android Low Memory Killer.

OpenClaw's gateway needs at minimum ~218 MB of heap for its startup dependencies. The first version of this hack set --max-old-space-size=384 as a safe starting point. Later hacks (#16 heap binary search, #20 v8 minimum) refined this down to the absolute minimum of 112 MB through systematic testing.

Before launching the gateway, heavy Google services need to be killed to free enough RAM. But killing Google Mobile Services (GMS) breaks WiFi — a lesson learned the hard way (Hack #24).

Implementation

Set the V8 heap limit via NODE_OPTIONS:

# Set heap limit — start conservative, then refine
export NODE_OPTIONS='--max-old-space-size=384'

# Kill heavy services to free RAM (but NEVER GMS!)
am force-stop com.android.chrome
am force-stop com.android.vending
am force-stop com.google.android.apps.docs
am force-stop com.google.android.apps.photos

# Launch gateway
npx openclaw gateway run --port 9000

The heap limit was progressively reduced through binary search testing:

# Evolution of the heap limit:
# v1: --max-old-space-size=384  (initial safe value)
# v2: --max-old-space-size=256  (after debloat)
# v3: --max-old-space-size=192  (after lazy loading)
# v4: --max-old-space-size=150  (after proot removal)
# v5: --max-old-space-size=128  (tight but works)
# v6: --max-old-space-size=112  (absolute minimum, live heap peaks at 93 MB)
# FAIL: --max-old-space-size=96 (OOM at startup — live heap peaks at 93 MB)

Verification

# Check current heap limit:
node -e "console.log(v8.getHeapStatistics().heap_size_limit / 1024 / 1024 + ' MB')"
# Expected: 384 MB (or whatever you set)

# Monitor heap usage during gateway startup:
node --max-old-space-size=384 -e "
  const v8 = require('v8');
  setInterval(() => {
    const h = v8.getHeapStatistics();
    console.log('Used:', (h.used_heap_size/1024/1024).toFixed(1), 'MB',
                'Total:', (h.total_heap_size/1024/1024).toFixed(1), 'MB');
  }, 1000);
"

Gotchas

  • NEVER kill com.google.android.gms (Google Mobile Services) — it manages WiFi on many Android devices. Killing it drops the WiFi connection entirely (Hack #24)
  • The heap limit is for V8 old space only. Total RSS includes young space, code space, stack, native allocations — typically 30-50 MB more than the heap limit
  • Setting the limit too low causes V8 to spend excessive time in GC before eventually crashing with FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed
  • --max-old-space-size must be set before Node.js starts (via NODE_OPTIONS or command-line flag), not at runtime

Result

MetricBeforeAfter
V8 heap limit~500 MB (auto)384 MB (explicit)
OOM killsFrequentRare
Gateway stabilityCrashes in <1 minStable for hours