Коротка відповідь
seniorВикористовую профайлер V8 (наприклад, Chrome DevTools або Node.js --inspect) для зняття heap snapshots і пошуку незвільних об’єктів. Після виявлення утримувачів, перевіряю колекцію замикань, таймерів і listeners; видаляю їх або застосовую WeakMap/WeakRef. Для запобігання – впроваджую автоматичний тест на утечку пам’яті (Jest + memwatch) і дотримуюсь патерну «dispose» у сервісах NestJS.
Повне пояснення
Що це і навіщо: Витік пам’яті – це ситуація, коли об’єкти залишаються в heap після того, як вони більше не потрібні, що призводить до зростання використання пам’яті і потенційного падіння сервера. Виявлення та усунення критично важливі для стабільності продакшн‑сервісів.
Ключові принципи/терміни:
- Heap snapshot – знімок пам’яті, що показує об’єкти і їх зв’язки.
- Garbage collector (GC) – автоматичний збирач сміття, який звільняє пам’ять.
- Reference cycle – цикл посилань, що заважає GC звільнити об’єкти.
- WeakMap / WeakRef – структури, що не блокують GC.
- Event loop – цикл подій Node.js, який обробляє таймери і listeners.
Як це працює:
- Під час роботи застосунку збираються heap snapshots у різних точках часу.
- Порівнюємо snapshot‑и, шукаємо збільшення кількості об’єктів одного типу.
- Аналізуємо граф посилань, щоб визначити «загублені» об’єкти.
- Визначаємо джерело утримання (замикання, таймери, listeners, глобальні змінні).
- Видаляємо або очищаємо ці ресурси.
Практика і реалізація:
// Приклад використання WeakMap для кешу, що не блокує GC
const cache = new WeakMap<object, any>();
function getCached(obj: object) {
if (!cache.has(obj)) cache.set(obj, computeExpensiveValue());
return cache.get(obj);
}
Тестування:
- Jest + memwatch-next
import memwatch from 'memwatch-next';
let leaked = false;
memwatch.on('leak', () => { leaked = true; });
// run code
expect(leaked).toBe(false);
- Node.js --inspect → Chrome DevTools → Memory → Take snapshot.
Безпека і приватність: неправильне управління пам’яттю може призвести до DoS‑атак. Перевірка розмірів об’єктів, обмеження кількості одночасних підключень і використання таймерів з setTimeout замість setInterval допомагає уникнути небажаних утримань.
Особливості в контексті NestJS/Express:
- Використовуйте
@Injectable()зscope: Scope.REQUESTдля об’єктів, що створюються на запит. - Підключайте
onModuleDestroy()для очищення таймерів і listeners.
Оптимізація, типові помилки та edge cases:
- Не зберігайте великі об’єкти у глобальних змінних.
- Уникайте безкінечних таймерів (
setIntervalбезclearInterval). - Перевіряйте колекції замикань у функціях, що повертають closures.
- Не зберігайте
req/resу замиканнях після завершення обробки. - Використовуйте
process.memoryUsage()для моніторингу в реальному часі. - Переконайтеся, що
EventEmitterне зберігає великі колекції listeners. - Уникайте
new Buffer()без вказівки розміру – використовуйтеBuffer.alloc. - Після видалення об’єктів перевіряйте, що GC звільнив пам’ять (наприклад, через
global.gc()у тестах).
Cheatsheet + follow‑up питання:
- Як вимкнути GC у Node.js?
node --expose-gc app.js→global.gc(). - Як перевірити, що об’єкт не утримується?
WeakRef+FinalizationRegistry. - Чому таймери можуть викликати утечку? Ті, що не очищуються після завершення.
- Як уникнути утримання
reqу замиканні? Використовуйте локальні змінні, а неthis. - Чи можна автоматизувати перевірку утечок у CI? Так, використовуючи
jest-memwatchабоnode-memwatch.