Як працює збір сміття в Node.js (V8)?
Збирання сміття в Node.js
Node.js використовує движок JavaScript V8, який застосовує автоматичний збірник сміття (GC) для управління пам'яттю. Розуміння його роботи є важливим для написання ефективних з точки зору пам'яті додатків.
Структура пам'яті V8
V8 ділить купу на дві основні області:
| Область | Призначення | Стратегія GC |
|---|---|---|
| Молода генерація (New Space) | Короткоживучі об'єкти | Scavenge (незначний GC) |
| Стара генерація (Old Space) | Довгоживучі об'єкти | Mark-Sweep-Compact (значний GC) |
┌─────────────────────────────────────────┐
│ V8 Heap │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ Young Gen │ │ Old Gen │ │
│ │ (1-8 MB) │ │ (до ~1.5 GB) │ │
│ │ ┌────┬────┐ │ │ │ │
│ │ │From│ To │ │ │ Mark-Sweep- │ │
│ │ │ │ │ │ │ Compact │ │
│ │ └────┴────┘ │ │ │ │
│ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────┘Scavenge (незначний GC) — Молода генерація
Працює за алгоритмом Чені (копіювання напівпростору):
- Нові об'єкти виділяються в "From" простір
- Коли "From" заповнюється, живі об'єкти копіюються в "To" простір
- "From" простір очищається повністю
- Простори обмінюються ролями
// Об'єкти починаються в Молодій генерації
let temp = { data: 'короткоживучий' }; // → Молод. Ген. (From простір)
temp = null; // підлягає GC на наступному зборі- Дуже швидко (копіює лише живі об'єкти, а не всі)
- Об'єкти, які пережили 2 збори, підвищуються до Старої генерації
Mark-Sweep-Compact (значний GC) — Стара генерація
Трифазний процес для довгоживучих об'єктів:
1. Фаза позначення
Починаючи з коренів GC (глобальні, стек), збирач проходить граф об'єктів, позначаючи всі досяжні об'єкти.
2. Фаза очищення
Всі непозначені об'єкти звільняються.
3. Фаза компактації (додаткова)
Пам'ять дефрагментується шляхом переміщення живих об'єктів разом.
Позначення: [●][○][●][○][○][●] (● = живий, ○ = мертвий)
Очищення: [●][ ][●][ ][ ][●]
Компактація: [●][●][●][ ]Інкрементний та паралельний GC
V8 не зупиняє світ для всього GC. Він використовує:
| Техніка | Опис |
|---|---|
| Інкрементне позначення | Позначення виконується маленькими кроками, чергуючи з виконанням JS |
| Паралельне очищення | Очищення відбувається на фонових потоках |
| Паралельна компактація | Часткова компактація на фонових потоках |
| Ліниве очищення | Сторінки очищуються лише тоді, коли виділення потребує цього |
Моніторинг GC
# Запустіть з трасуванням GC
node --trace-gc server.js
# Приклад виходу:
# [12345:0x1] 52 ms: Scavenge 4.2 (6.3) -> 3.8 (8.3) MB, 1.2 / 0.0 ms
# [12345:0x1] 102 ms: Mark-sweep 8.1 (12.3) -> 6.2 (12.3) MB, 5.1 / 0.0 ms// Відкрити GC для ручного виклику (тільки для тестування!)
// node --expose-gc script.js
if (global.gc) {
global.gc(); // Примусове збирання сміття
}
// Використання хуків продуктивності
const { PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(`GC: ${entry.kind} - ${entry.duration.toFixed(2)}ms`);
});
});
obs.observe({ entryTypes: ['gc'] });Обмеження пам'яті V8
| Платформа | За замовчуванням обмеження купи |
|---|---|
| 64-біт | ~1.5 GB |
| 32-біт | ~512 MB |
Перезаписати з:
node --max-old-space-size=4096 server.js # 4GB
node --max-semi-space-size=64 server.js # 64MB молода генНайкращі практики
| Практика | Чому |
|---|---|
| Уникайте великих довгоживучих об'єктів | Зменшує тиск на Стару Генерацію |
Використовуйте WeakMap / WeakRef для кешів | Дозволяє GC збирати записи |
| Уникайте замикань, які захоплюють великі області | Запобігає ненавмисному утриманню |
| Передавайте великі дані замість буферизації | Тримає Молоду Генерацію маленькою |
Моніторте з --trace-gc на стадії | Виявляйте паузи GC на ранніх етапах |
Ключове усвідомлення: Більшість проблем з продуктивністю в Node.js викликані надмірними паузами GC у Старій Генерації. Тримайте свою Стару Генерацію стрункою, уникаючи необмежених кешів, замикань над великими об'єктами та завжди очищаючи посилання.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.