← Назад к вопросам
Как обнаружить и исправить утечку памяти в Node.js приложении?
2.2 Middle🔥 81 комментариев
#Node.js и JavaScript#Кэширование и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Утечка памяти — это когда приложение занимает всё больше памяти со временем, хотя она уже не нужна. Это серьёзная проблема, снижающая производительность и приводящая к краху сервера.
Как обнаружить утечку памяти
1. Мониторинг использования памяти
const os = require('os');
const v8 = require('v8');
// Проверка памяти каждые 10 сек
setInterval(() => {
const used = process.memoryUsage();
console.log('Memory Usage:');
console.log(` RSS: ${Math.round(used.rss / 1024 / 1024)} MB`);
console.log(` Heap Used: ${Math.round(used.heapUsed / 1024 / 1024)} MB`);
console.log(` Heap Total: ${Math.round(used.heapTotal / 1024 / 1024)} MB`);
console.log(` External: ${Math.round(used.external / 1024 / 1024)} MB`);
}, 10000);
2. Heap Snapshot (снимки памяти)
const v8 = require('v8');
const fs = require('fs');
const path = require('path');
function writeHeapSnapshot() {
const fileName = `heap-${Date.now()}.heapsnapshot`;
const snapshotPath = path.join(__dirname, fileName);
const snapshot = v8.writeHeapSnapshot(snapshotPath);
console.log(`Heap snapshot написан в ${snapshotPath}`);
return snapshotPath;
}
// Записать снимок
setTimeout(() => {
writeHeapSnapshot();
}, 30000);
3. Использование Chrome DevTools
node --inspect app.js
# Открыть chrome://inspect в браузере Chrome
4. Инструменты профилирования
# Использование clinic.js
npm install -g clinic
clinic doctor -- node app.js
# Использование node-gyp и профилировщиков
npm install heapdump
Типичные причины утечек памяти
1. Глобальные переменные
// ПЛОХО - утечка памяти
global.cachedUsers = [];
app.get('/users', (req, res) => {
global.cachedUsers.push({id: 1, name: 'User'});
// Массив растет бесконечно!
res.json(global.cachedUsers);
});
// ХОРОШО - ограниченный размер
const cache = new Map();
const MAX_CACHE_SIZE = 1000;
app.get('/users', (req, res) => {
if (cache.size > MAX_CACHE_SIZE) {
cache.clear();
}
// ...
});
2. Event Listeners без удаления
// ПЛОХО - утечка памяти
const EventEmitter = require('events');
const emitter = new EventEmitter();
for (let i = 0; i < 10000; i++) {
emitter.on('data', () => {
console.log('Data received');
});
}
// Warning: Possible EventEmitter memory leak detected
// ХОРОШО - удалять слушателей
emitter.off('data', handler);
emitter.removeAllListeners('data');
3. Неудаленные таймеры
// ПЛОХО
app.get('/stream', (req, res) => {
const interval = setInterval(() => {
res.write('data\n');
}, 1000);
// Если клиент отключится, интервал остается активным!
});
// ХОРОШО
app.get('/stream', (req, res) => {
const interval = setInterval(() => {
res.write('data\n');
}, 1000);
req.on('close', () => {
clearInterval(interval);
});
});
4. Циклические ссылки
// ПЛОХО - может создать утечку в старых версиях
class Node {
constructor(value) {
this.value = value;
this.parent = null;
this.children = [];
}
}
const parent = new Node('parent');
const child = new Node('child');
parent.children.push(child);
child.parent = parent;
// Циклические ссылки могут задержать garbage collection
// ХОРОШО - использовать WeakMap для обратных ссылок
const parentMap = new WeakMap();
const parent2 = new Node('parent');
const child2 = new Node('child');
parent2.children.push(child2);
parentMap.set(child2, parent2); // Не создает утечку
5. Неправильное использование промисов
// ПЛОХО - утечка памяти
const promises = [];
app.get('/request', async (req, res) => {
const p = new Promise(resolve => {
// Никогда не разрешается
setTimeout(() => {}, 999999999);
});
promises.push(p);
res.json({ok: true});
});
// ХОРОШО - управлять промисами
app.get('/request', async (req, res) => {
try {
const result = await Promise.race([
fetchData(),
timeout(5000) // Timeout для предотвращения бесконечного ожидания
]);
res.json(result);
} catch (e) {
res.status(500).json({error: e.message});
}
});
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
}
Исправление утечек памяти
1. Использовать обработчик cleanup при требуемых условиях
class DatabaseConnection {
constructor(url) {
this.url = url;
this.listeners = [];
}
on(event, handler) {
this.listeners.push({event, handler});
}
close() {
// Очистить все слушатели
this.listeners = [];
}
}
2. Ограничивать размеры кэшей
const LRU = require('lru-cache');
const cache = new LRU({
max: 500, // максимум элементов
maxAge: 1000 * 60 * 60 // максимум 1 час
});
cache.set('key', 'value');
cache.get('key');
3. Использовать garbage collection явно (с осторожностью)
if (global.gc) {
console.log('GC включен');
global.gc(); // Требует --expose-gc при запуске
} else {
console.log('node --expose-gc app.js');
}
Инструменты для анализа
# Clinic.js для диагностики
node --max-old-space-size=4096 --inspect app.js
# Использование node-inspector
npm install -g node-inspector
node-debug app.js
# node-memwatch
npm install memwatch-next
Best Practices
-
Удаляй event listeners
emitter.removeListener('event', handler); -
Очищай таймеры
clearTimeout(timeout); clearInterval(interval); -
Используй WeakMap для кэшей объектов
const cache = new WeakMap(); -
Ограничивай размеры коллекций
if (array.length > MAX_SIZE) array.shift(); -
Проверяй вывод memory usage регулярно
console.log(process.memoryUsage());
Вывод
Для обнаружения и исправления утечек:
- Мониторь memory usage
- Делай heap snapshots
- Используй DevTools и профилировщики
- Удаляй listeners и таймеры
- Ограничивай размеры кэшей
- Тестируй долгоживущие приложения