Перейти к содержимому

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 — переиспользование вместо allocation
class 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 → release
function 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-place
function 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.js
const { 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.