← Назад к вопросам

Как обнаружить и исправить утечку памяти в 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

  1. Удаляй event listeners

    emitter.removeListener('event', handler);
    
  2. Очищай таймеры

    clearTimeout(timeout);
    clearInterval(interval);
    
  3. Используй WeakMap для кэшей объектов

    const cache = new WeakMap();
    
  4. Ограничивай размеры коллекций

    if (array.length > MAX_SIZE) array.shift();
    
  5. Проверяй вывод memory usage регулярно

    console.log(process.memoryUsage());
    

Вывод

Для обнаружения и исправления утечек:

  1. Мониторь memory usage
  2. Делай heap snapshots
  3. Используй DevTools и профилировщики
  4. Удаляй listeners и таймеры
  5. Ограничивай размеры кэшей
  6. Тестируй долгоживущие приложения
Как обнаружить и исправить утечку памяти в Node.js приложении? | PrepBro