Dual-Engine Architecture: Node.js & Memory Management
Deep-dive on CPython vs V8 Garbage Collection, libuv stream buffers, and Android LMK survival strategies.
1. CPython vs V8 Memory & GC Divergence on Android
Python relies on deterministic Reference Counting to immediately free memory upon scope exit. In contrast, Node.js V8 uses Generational Scavenge & Mark-Sweep-Compact with Lazy GC, keeping heap allocated until pressure builds. On mobile devices with 1GB-4GB RAM, default V8 heap limits (1.4GB) trigger Android Low Memory Killer (LMK) execution.
| Dimension | CPython Runtime (Python) | V8 Engine Runtime (Node.js) | Mobile Android Termux Impact |
|---|---|---|---|
| GC Trigger | Deterministic Ref-Count (0-sec deallocation) | Generational Lazy GC (waits for heap threshold) | Node.js requires explicit memory caps |
| Default Heap Cap | OS-governed dynamic RAM | 1.4 GB ~ 4 GB desktop default | Can trigger Android LMK OOM on <=4GB phones |
| Exit Lifecycle | Synchronous / async exit hooks allowed | Event loop is dead inside process.on('exit') | Reaper MUST use 100% sync C-syscalls |
| Crash Propagation | Traceback on unhandled exception | Unhandled Promise rejection can kill process | Requires unhandledRejection global guard |
2. Hardened Runtime Protections (Audit Actions Applied)
🛡️ Synchronous Signal & Exit Reaper
In Node.js, process.on("exit") permanently shuts down the event loop—async calls are ignored. ProcessReaper uses pure synchronous C-level process.kill and fs.unlinkSync to guarantee zero zombie leaks.
🛡️ Uncaught Crash Handlers (uncaughtException & unhandledRejection)
Unhandled Promise rejections and uncaught exceptions automatically trigger synchronous ProcessReaper.killAllTracked() before process termination.
🛡️ V8 Heap Capping & forceGarbageCollection()
Low-memory mode caps V8 heap at 128MB. The forceGarbageCollection() helper flushes V8 young/old generation heaps during long-running crawler cycles.
3. Node.js / TypeScript Production Recipes
JavaScript (ESM / CommonJS):
const { launch, setupStealthContext, blockHeavyResources, forceGarbageCollection } = require('termux-playwright');
async function main() {
// 1. Launch with low memory mode & WakeLock
const browser = await launch({
headless: true,
stealth: true,
lowMemoryMode: true,
wakeLock: true
});
try {
const context = await setupStealthContext(browser, {
locale: 'en-US',
timezoneId: 'America/New_York'
});
const page = await context.newPage();
// 2. Abort heavy media to save mobile data & CPU
await blockHeavyResources(page, { images: true, media: true, fonts: true });
await page.goto('https://news.ycombinator.com', { timeout: 45000, waitUntil: 'domcontentloaded' });
console.log('Page Title:', await page.title());
// 3. Periodic memory purge for long-running scrapers
forceGarbageCollection();
} finally {
await browser.close();
}
}
main().catch(console.error);
4. 24/7 Unattended Mobile Daemon with PM2
To run your Node.js crawler 24/7 in Termux without process teardown when Termux is backgrounded, use PM2:
# Install PM2 globally in Termux
npm install -g pm2
# Start crawler with V8 memory cap and auto-restart
pm2 start app.js --name "mobile-crawler" --node-args="--max-old-space-size=256 --expose-gc"
# View live logs & memory
pm2 logs mobile-crawler
pm2 monit