Обложка: Что делать с ошибкой «Heap out of memory» в JavaScript

Что делать с ошибкой «Heap out of memory» в JavaScript

javascript heap out of memory

Ошибка «Heap out of memory» в JavaScript возникает когда приложению недостаточно памяти. В этой статье мы разберемся как быстро исправить эту ошибку.

Как исправить «Heap out of memory» в JavaScript

Самый быстрый способ — увеличить количество памяти в Node.js. Начиная с версии v8 вы можете устанавливать ограничение в мегабайтах с помощью флага  --max-old-space-size:

node --max-old-space-size=4096 index.js

Вы можете установить любое ограничение, но не используйте всю доступную память, иначе может произойти крэш системы.

Аналогичного эффекта можно добиться с помощью другого флага:

NODE_OPTIONS="--max-old-space-size=4096" node index.js

Изменение ограничения памяти для всей среды Node.js

Чтобы изменить лимит памяти для всей среды, нужно установить значение переменной NODE_OPTIONS в конфигурационном файле (его расширение .bashrc, bash_profile или .zshrc и т. п.).

export NODE_OPTIONS=--max_old_space_size=4096

«Heap out of memory» во время nmp install

Если во время установки пакетов с помощью npn или yarn у вас появляется эта ошибка, вы можете увеличить лимит памяти на время установки.

node --max-old-space-size=4096 $(which npm) install -g nextawesomelib

Что означает эта ошибка?

По умолчанию в Node.js установлен лимит памяти, который не позволяет программе занять слишком много памяти и уронить всю систему. Лимит отличается на разных версиях Node.js и архитектурах (32бита или 64бита).

Ограничения памяти на разных версиях Node.js

Эти значения не объявлены официально, но с помощью небольшой программы можно получить такие значения для 64 битной архитектуры.

Лимиты памяти на разных версиях Node.js

4GB памяти в куче будет достаточно для большинства случаев

Чтобы проверить лимит памяти вашей системы, создайте файл index.js и добавьте в него следующий код:

const array = [];
while (true) {
  // увеличение массива на каждой итерации
  array.push(new Array(10000000));

  const memory = process.memoryUsage();
  console.log((memory.heapUsed / 1024 / 1024 / 1024).toFixed(4), 'GB');
}

Как избежать недостатка памяти в Node.js

Увеличение лимита памяти поможет быстро исправить проблему, но этого может быть недостаточно. В следующий раз память системы может закончиться. В любом случае стоит выяснить источник проблемы.

Вот три альтернативных решения, которые позволят уменьшить потребление памяти.

Обработка данных по частям

Иногда нужно обработать большой набор данных. Например, вы пишите программу, которая принимает данные из CSV файла, очищает их и добавляет в БД (это называется ETL: извлечение, трансформация, загрузка).

Если в такой ситуации программе начинает не хватать памяти, попробуйте разделить данные на несколько частей.

# split -l numberoflines filename
split -l 1000000 users.csv

Подробнее о том, как сделать это в MongoDB в этом ответе на StackOverflow.

Избегайте утечек памяти

В этой статье объясняется, как работает управление памятью в JavaScript, и как избежать большинства возможных утечек.

Её содержание сводится к тому, что большинство утечек, которые можно отследить, вызваны неудалёнными ссылками на объекты, которые больше не нужны. Это может случиться, когда вы забыли удалить interval, timer или чрезмерно используете глобальные переменные.

Профилирование

Профилирование помогает обнаружить утечки памяти. На фронтенде это можно сделать в Chrome в Инструментах разработчика во вкладке Memory.

В Node.js начиная с версии 6.3.0 также можно использовать Chrome для отладки использования памяти.

Во-первых, запустите приложение в режиме проверки:

node --inspect index.js

Затем откройте страницу в Chrome, введите адрес chrome://inspect и нажмите на кнопку Open dedicated DevTools for Node.

После этого откроется окно, в котором вы сможете подключиться к вашему Node.js приложению.

Devtools Node.js

Перезапуск процессов

Допустим, ваша программа работает на компьютере с ограниченным объёмом памяти, например Raspberry Pi.

Мы будем использовать cluster и библиотеки node v8.

Cluster даёт возможность воспользоваться преимуществами многоядерных систем и запускать кластер из процессов Node.js.

V8 предоставляет API для конкретной версии V8, используемой в Node.js.

Давайте разделим программу на две сущности: master и worker.

Master будет перезапускать worker`ов в случае, если они перестанут работать из-за переполнения кучи. Worker`ы будут отвечать за основную логику (в нашем случае запускать тяжёлую функцию heavyHeapConsumer).

const cluster = require('cluster');
const v8 = require('v8');

let heavyHeapConsumer = () => {
  let arrays = [];
  setInterval(() => {
    arrays.push(new Array(1000000));
  }, 100);
};

if (cluster.isMaster) {
  cluster.fork();
  cluster.on('exit', (deadWorker, code, signal) => {
    // Перезапуск worker`а
    let worker = cluster.fork();
    
    // Сохранение id процесса
    let newPID = worker.process.pid;
    let oldPID = deadWorker.process.pid;
    
    // Логгирование
    console.log('worker ' + oldPID + ' died.');
    console.log('worker ' + newPID + ' born.');
  });
} else { // worker
  const initialStats = v8.getHeapStatistics();
  
  const totalHeapSizeThreshold = 
    initialStats.heap_size_limit * 85 / 100;
  console.log("totalHeapSizeThreshold: " + totalHeapSizeThreshold);
  
  let detectHeapOverflow = () => {
    let stats = v8.getHeapStatistics();
    
    console.log("total_heap_size: " + (stats.total_heap_size));
    
    if ((stats.total_heap_size) > totalHeapSizeThreshold) {
      process.exit();
    }
  };
  setInterval(detectHeapOverflow, 1000);
  
  // выполнение основной логики
  heavyHeapConsumer();
}

При первом запуске приложения создается worker и подписка на событие exit, при срабатывании которой создаётся новый worker, и событие логгируется.

total_heap_size — размер кучи, который можно увеличить.

heap_size_limit — максимально возможный размер кучи.

В коде worker`а устанавливается total_heap_size равный 85% от heap_size_limit. Затем worker каждую секунду проверяет не превышен ли лимит. Если лимит превышен, то процесс worker убивает себя.

Лимит (85%) и интервал проверки (1 секунда) нужно выбирать для каждого конкретного случая. Здесь функция heavyHeapConsumer увеличивает кучу каждые 100мс. Если в вашем варианте увеличение будет происходить каждые 10мс, то следует уменьшить лимит и увеличить интервал проверки.

Полный код примера доступен на GitHub.

Источники JavaScript Heap Out Of Memory Error, Detect heap overflow on Node.js: JavaScript heap out of memory