Garbage Collection
V8 использует generational GC: быстрый Scavenge для young generation и полный tri-color Mark-Sweep-Compact для old generation; incremental и concurrent marking минимизируют паузы — знание этого критично для GC-friendly кода.
V8 Generational GC Young Generation (New Space, ~1–8 MB) ┌────────────────────────────────────────┐ │ Semi-space “From” │ Semi-space “To” │ │ [obj1][obj2][obj3] │ (пусто) │ └────────────────────────────────────────┘ │ Minor GC (Scavenge) ~0.5–2ms │ Живые объекты копируются в “To” │ “From” ↔ “To” меняются местами │ Дважды выжившие → promoted → Old Space ▼ Old Generation (Old Space, ~256 MB+) ┌────────────────────────────────────────┐ │ [long-lived objects…] │ └────────────────────────────────────────┘ │ Major GC (Mark-Sweep-Compact) │ │ Mark phase — tri-color marking: │ ┌────────────────────────────┐ │ │ white = не посещён │ │ │ grey = посещён, дети нет │ │ │ black = посещён + дети │ │ │ Roots → grey → black │ │ │ Все white после → мусор │ │ └────────────────────────────┘ │ Sweep: освободить white объекты │ Compact: дефрагментация (опционально) │ │ Incremental marking: шаги 1–5ms между tasks │ Concurrent marking: параллельные потоки (V8 7.0+) │ Concurrent sweeping: фоновая очистка ▼ Pause time: Minor ~1ms, Major ~5–50ms (incremental)
// GC-friendly: object pooling — переиспользование вместо allocationclass ObjectPool { #free = []; #create; #reset; constructor(create, reset) { this.#create = create; this.#reset = reset; }
acquire() { return this.#free.pop() ?? this.#create(); } release(obj) { this.#reset(obj); this.#free.push(obj); }}
const pool = new ObjectPool( () => ({ x: 0, y: 0, data: null }), o => { o.x = 0; o.y = 0; o.data = null; });
// В hot loop: acquire → use → releasefunction processFrame(entities) { for (const e of entities) { const vec = pool.acquire(); computeVelocity(e, vec); pool.release(vec); // 0 GC pressure }}// Избегать allocation в hot loops// BAD: создаёт N новых объектов каждый кадрfunction updateBad(entities) { return entities.map(e => ({ x: e.x + e.vx, y: e.y + e.vy })); // N allocs}
// GOOD: mutate in-placefunction updateGood(entities) { for (const e of entities) { e.x += e.vx; e.y += e.vy; } // 0 allocs}
// Closure и memory leak: захват больших объектовlet largeBuffer = new ArrayBuffer(10 * 1024 * 1024);function createHandler() { // handler захватывает largeBuffer через замыкание! return () => process(largeBuffer);}// Решение: явная передача, или largeBuffer = null после использования// Мониторинг GC в Node.jsconst { PerformanceObserver, performance } = require('perf_hooks');
const obs = new PerformanceObserver(list => { for (const entry of list.getEntries()) { const { kind, flags } = entry.detail; // gcType, gcFlags console.log(`GC: ${entry.entryType} kind=${kind} flags=${flags} ${entry.duration.toFixed(1)}ms`); }});obs.observe({ entryTypes: ['gc'] });
// node --expose-gc → global.gc() для явного вызова в тестахif (global.gc) global.gc();const { heapUsed } = process.memoryUsage();console.log('heap after GC:', (heapUsed / 1024 / 1024).toFixed(1), 'MB');Итог: V8 GC разделён на быстрый Minor (Scavenge, young gen) и полный Major (tri-color mark-and-sweep, old gen); incremental/concurrent marking минимизирует паузы; GC pressure зависит от allocation rate в hot paths.