Node js увеличить память

Обновлено: 04.07.2024

Сегодня я запустил свой скрипт для индексации файловой системы, чтобы обновить индекс файлов RAID, и через 4 часа он вылетел со следующей ошибкой:

Сервер оснащен 16 ГБ оперативной памяти и 24 ГБ подкачки SSD. Я очень сомневаюсь, что мой скрипт превысил 36 ГБ памяти. По крайней мере, не должно

Скрипт создает индекс файлов, хранящихся в виде массива объектов с метаданными файлов (даты изменения, разрешения и т. Д., Без больших данных)

Я уже сталкивался со странными проблемами с узлами в прошлом с этим скриптом, что заставило меня, например. разделить индекс на несколько файлов, поскольку узел давал сбой при работе с такими большими файлами, как String. Есть ли способ улучшить управление памятью nodejs с огромными наборами данных?

Если я правильно помню, существует строгое стандартное ограничение на использование памяти в V8 около 1,7 ГБ, если вы не увеличиваете его вручную.

В одном из наших продуктов мы использовали это решение в сценарии развертывания:

На всякий случай кто-то столкнется с этим в среде, где они не могут напрямую устанавливать свойства узла (в моем случае инструмент сборки):

Вы можете установить параметры узла с помощью переменной среды, если вы не можете передать их в командной строке.

Если вы хотите увеличить использование памяти узлом глобально, а не только одним скриптом, вы можете экспортировать переменную среды, например:
export NODE_OPTIONS=--max_old_space_size=4096

Тогда вам не нужно играть с файлами при запуске таких сборок, как npm run build .

  • 1 Для дополнительного удобства добавьте в профиль bash_profile или zsh.

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

1 ГБ - 8 ГБ

  • можно ли увеличивать его в степени двойки? следует ли устанавливать его больше, чем системная память? Если нет, то какое соотношение системной памяти к максимальному размеру старого пространства?
  • 3 @HarryMoreno Фактически вы можете ввести любое числовое значение, которое вам нравится. Не обязательно должно быть в степени 2. Однако не уверен в соотношении. Это только максимальный предел, он не будет использовать всю память. Я бы просто установил его настолько высоко, насколько вам нужно, а затем уменьшил бы, если необходимо.
  • Дам системную оперативную память - 1гб попробовать. Предполагая, что эта виртуальная машина предназначена только для запуска этого приложения узла.
  • 2 @HarryMoreno Отношение хорошей системной памяти к максимальному размеру старого пространства полностью зависит от того, что еще работает на вашем компьютере. Вы можете увеличить его в степени двойки или использовать любое число. Вы можете установить его больше, чем системная память, но вы столкнетесь с проблемами подкачки.

Я столкнулся с этой проблемой при попытке отладки с помощью VSCode, поэтому просто хотел добавить, как вы можете добавить аргумент в свою настройку отладки.

Вы можете добавить его в runtimeArgs свойство вашей конфигурации в launch.json .

  • 1 Будет ли launch.json тем же, что и package.json?
  • Нет launch.json - это файл конфигурации, специально предназначенный для запуска приложения из VS Code.
  • Где этот файл launch.json для VS Code?

Я боролся с этим даже после установки --max-old-space-size.

Затем я понял, что перед скриптом кармы нужно поставить параметры --max-old-space-size.

также лучше всего указать оба синтаксиса --max-old-space-size и --max_old_space_size мой скрипт для кармы:

У меня была аналогичная проблема при сборке AOT angular. Мне помогли следующие команды.

  • 1 У меня сработало ура, может понадобиться sudo npm -g install increase-memory-limit --unsafe-perm
  • 4к маловато. разработчик оставался на 4к как статичный. хорошее решение от разработчика. Кроме того, когда я просматривал страницу npm, я не видел информации об изменении предельного значения. На самом деле решение есть, но оно не сработало.

Я только что столкнулся с той же проблемой с моим экземпляром EC2 t2.micro, который имеет 1 ГБ памяти.

Я решил проблему, создав файл подкачки с использованием этого URL-адреса и установив следующую переменную среды.

Наконец проблема ушла.

Я надеюсь, что это будет полезно в будущем.

Действия по устранению этой проблемы (в Windows) -

  1. Откройте командную строку и введите %appdata% нажмите Ввод
  2. Перейдите к %appdata% > папка npm
  3. Открыть или изменить ng.cmd в вашем любимом редакторе
  4. Добавить --max_old_space_size=8192 в блок IF и ELSE

Ваш node.cmd после изменения файл выглядит так:

  • Это зависит от Windows. В ОП ничего не упоминалось о Windows.
  • @DanDascalescu Обновленный ответ. Спасибо за уведомление

Я пробовал приведенный ниже код, и он работает нормально .

выполните команду cmd, чтобы установить новый размер.

fwiw, может помочь поиск и исправление проблемы с памятью с помощью чего-то вроде memwatch.

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

Синтаксис

пример

Почему размер равен 16000 в максимальном старом пространстве?

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

Как проверить и подобрать нужный размер?

Это в основном остается в нашем движке v8 . приведенный ниже код поможет вам понять размер кучи вашего локального узла v8.

Можно использовать следующую переменную среды:

Другое примечание: console.log() также потребляет память в терминале.

просто попытался прокомментировать console.log () на терминале. потому что это также займет память.

если вы хотите глобально изменить память для узла (Windows), перейдите в расширенные настройки системы -> переменные среды -> новая пользовательская переменная

Я просто хочу добавить, что в некоторых системах, даже увеличивая предел памяти узла с помощью --max-old-space-size , этого недостаточно и возникает такая ошибка ОС:

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

Вы можете проверить max_map_count, запустив

и увеличить его, запустив

и исправьте его, чтобы он не сбрасывался после перезагрузки, добавив эту строку

в /etc/sysctl.conf файл.

Проверьте здесь для получения дополнительной информации.

Хороший метод анализа ошибки - запустить процесс с strace

В моем случае я обновил версию node.js до последней (версия 12.8.0), и она работала как шарм.

  • Привет, Анджела и добро пожаловать в SO! Не могли бы вы указать точную версию Node.js, до которой вы обновили, для будущих читателей? Благодарность!
  • Я обновился до последней версии LTS: 12.18.0
  • Не думал, что это сработает, но это действительно так! Итак, для всех, кто читает это, и их версия Node устарела, попробуйте обновить до последней версии (я перешел с версии 10 на 14.8), вероятно, это решит эту проблему для вас. Большое спасибо

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

Если вы пытаетесь запустить не node сам, а какой-то другой софт, например webpack вы можете использовать переменную среды и cross-env пакет:

Для сборки углового проекта я добавил строку ниже в мой pakage.json файл в скрипты раздел.

Теперь, чтобы связать свой код, я использую npm run build-prod вместо того ng build --requiredFlagsHere

надеюсь это поможет!

Для других новичков, таких как я, которые не нашли подходящего решения для этой ошибки, проверьте версия узла установлен (x32, x64, x86). У меня 64-битный процессор, и я установил версию узла x86, что вызвало CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory ошибка.

Обновите узел до последней версии. Я был на узле 6.6 с этой ошибкой и обновился до 8.9.4, и проблема исчезла.

В моем случае я сбежал npm install на предыдущей версии узла, через день я обновил версию узла и ram npm install для нескольких модулей. После этого я получал эту ошибку. Чтобы решить эту проблему, я удалил папку node_module из каждого проекта и запустил npm install еще раз.

Надеюсь, это поможет решить проблему.

Примечание. Это происходило на моем локальном компьютере и было исправлено только на локальном компьютере.

При запуске Node.js-приложений в контейнерах Docker традиционные настройки памяти не всегда работают так, как ожидается. Материал, перевод которого мы сегодня публикуем, посвящён поиску ответа на вопрос о том, почему это так. Здесь же будут приведены практические рекомендации по управлению памятью, доступной Node.js-приложениям, работающим в контейнерах.




Обзор рекомендаций

Предположим, Node.js-приложение выполняется в контейнере с установленным лимитом памяти. Если речь идёт о Docker, то для установки этого лимита могла быть использована опция --memory . Нечто подобное возможно и при работе с системами оркестрации контейнеров. В таком случае рекомендуется, при запуске Node.js-приложения, использовать опцию --max-old-space-size . Это позволяет сообщить платформе о том, какой объём памяти ей доступен, а так же учесть то, что этот объём должен быть меньше лимита, заданного на уровне контейнера.

Когда Node.js-приложение выполняется внутри контейнера, задавайте ёмкость доступной ему памяти в соответствии с пиковым значением использования активной памяти приложением. Это делается в том случае, если ограничения памяти контейнера можно настраивать.

Теперь поговорим о проблеме использования памяти в контейнерах подробнее.

Лимит памяти Docker

По умолчанию контейнеры не имеют ограничений по ресурсам и могут использовать столько памяти, сколько позволяет им операционная система. У команды docker run имеются опции командной строки, позволяющие задавать лимиты, касающиеся использования памяти или ресурсов процессора.

Команда запуска контейнера может выглядеть так:


Обратите внимание на следующее:

  • x — это лимит объёма памяти, доступной контейнеру, выраженный в единицах измерения y .
  • y может принимать значение b (байты), k (килобайты), m (мегабайты), g (гигабайты).


Здесь лимит памяти установлен в 1000000 байт.

Для проверки лимита памяти, установленного на уровне контейнера, можно, в контейнере, выполнить следующую команду:


Поговорим о поведении системы при указании с помощью ключа --max-old-space-size лимита памяти Node.js-приложения. При этом данный лимит памяти будет соответствовать лимиту, установленному на уровне контейнера.

То, что в имени ключа называется «old-space», представляет собой один из фрагментов кучи, управляемой V8 (то место, где размещаются «старые» JavaScript-объекты). Этот ключ, если не вдаваться в детали, которых мы коснёмся ниже, контролирует максимальный размер кучи. Подробности о ключах командной строки Node.js можно почитать здесь.

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

В следующем примере (файл приложения называется test-fatal-error.js ) в массив list , с интервалом в 10 миллисекунд, помещают объекты MyRecord . Это приводит к бесконтрольному росту кучи, имитируя утечку памяти.


Обратите внимание на то, что все примеры программ, которые мы будем тут рассматривать, помещены в образ Docker, который можно загрузить с Docker Hub:


Вы можете воспользоваться этим образом для самостоятельных экспериментов.

Кроме того, можно упаковать приложение в контейнер Docker, собрать образ и запустить его с указанием лимита памяти:


Здесь ravali1906/dockermemory — это имя образа.

Теперь можно запустить приложение, указав лимит памяти для него, превышающий лимит контейнера:


Здесь ключ --max_old_space_size представляет собой лимит памяти, указываемый в мегабайтах. Метод process.memoryUsage() даёт сведения об использовании памяти. Значения показателей выражены в байтах.

Работа приложения в некий момент времени принудительно завершается. Происходит это тогда, когда объём использованной им памяти переходит некую границу. Что это за граница? О каких ограничениях на объём памяти можно говорить?

Ожидаемое поведение приложения, запущенного с ключом --max-old-space-size

По умолчанию максимальный размер кучи в Node.js (вплоть до версии 11.x) составляет 700 Мб на 32-битных платформах, и 1400 Мб на 64-битных. О настройке этих значений можно почитать здесь.

В теории, если установить с помощью ключа --max-old-space-size лимит памяти, превышающий лимит памяти контейнера, можно ожидать, что приложение будет завершено защитным механизмом ядра Linux OOM Killer.

В реальности этого может и не случиться.

Реальное поведение приложения, запущенного с ключом --max-old-space-size

Приложению, сразу после запуска, не выделяется вся память, лимит которой указан с помощью --max-old-space-size . Размер JavaScript-кучи зависит от нужд приложения. О том, какой размер памяти использует приложение, можно судить на основании значения поля heapUsed из объекта, возвращаемого методом process.memoryUsage() . Фактически, речь идёт о памяти, выделенной в куче под объекты.

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

Но в реальности этого тоже может не случиться.

При профилировании ресурсоёмких Node.js-приложений, которые запущены в контейнерах с заданным лимитом памяти, можно наблюдать следующие паттерны:

  1. OOM Killer срабатывает гораздо позже того момента, когда значения heapTotal и heapUsed оказываются значительно превышающими ограничения на объём памяти.
  2. OOM Killer никак не реагирует на превышение ограничений.

Объяснение особенностей поведения Node.js-приложений в контейнерах

Контейнер наблюдает за одним важным показателем приложений, которые в нём выполняются. Это — RSS (resident set size). Этот показатель представляет некую часть виртуальной памяти приложения.

Более того, он представляет собой фрагмент памяти, которая выделена приложению.

Но и это ещё не всё. RSS — это часть активной памяти, выделенной приложению.

Активной может быть не вся память, выделенная приложению. Дело в том, что «выделенная память» не обязательно физически выделяется до тех пор, пока процесс не начнёт ей по-настоящему пользоваться. Кроме того, в ответ на запросы на выделение памяти от других процессов, операционная система может сбросить в файл подкачки неактивные фрагменты памяти приложения и передать освободившееся пространство другим процессам. А когда приложению снова понадобятся эти фрагменты памяти, они будут взяты из файла подкачки и возвращены в физическую память.

Показатель RSS указывает на объём активной и доступной приложению памяти в его адресном пространстве. Именно он влияет на принятие решения о принудительном завершении работы приложения.

Доказательства

▍Пример №1. Приложение, которое выделяет память под буфер

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


Для того чтобы объём памяти, выделяемой программой, превысил бы лимит, заданный при запуске контейнера, сначала запустим контейнер следующей командой:


После этого запустим программу:


Как видно, система не завершила выполнение программы, хотя при этом выделенная программой память и превышает лимит контейнера. Произошло это из-за того, что программа не работает со всей выделенной памятью. Показатель RSS очень мал, он не превышает лимит памяти контейнера.

▍Пример №2. Приложение, заполняющее буфер данными

В следующем примере, buffer_example_fill.js , память не просто выделяется, а ещё и заполняется данными:


После этого запустим приложение:


Как видно, даже теперь приложение не завершается! Почему? Дело в том, что когда объём активной памяти достигает лимита, заданного при запуске контейнера, и при этом в файле подкачки есть место, некоторые из старых страниц памяти процесса перемещаются в файл подкачки. Освобождённая память оказывается доступной тому же самому процессу. По умолчанию Docker выделяет под файл подкачки пространство, равное лимиту памяти, заданному с помощью флага --memory . Учитывая это можно сказать, что у процесса есть 2 Гб памяти — 1 Гб в активной памяти, и 1 Гб — в файле подкачки. То есть, благодаря тому, что приложение может пользоваться своей же памятью, содержимое которой временно перемещается в файл подкачки, размер показателя RSS находится в пределах лимита контейнера. В результате приложение продолжает работать.

▍Пример №3. Приложение, заполняющее буфер данными, выполняющееся в контейнере, в котором файл подкачки не используется

Вот код, с которым мы будем здесь экспериментировать (это — тот же файл buffer_example_fill.js ):


На этот раз запустим контейнер, явным образом настроив особенности работы с файлом подкачки:

Общие рекомендации

Когда Node.js-приложения запускают с ключом --max-old-space-size , значение которого превышает лимит памяти, заданный при запуске контейнера, может показаться, что Node.js «не обращает внимания» на лимит контейнера. Но, как видно из предыдущих примеров, явной причиной подобного поведения является тот факт, что приложение просто не использует весь объём кучи, заданный с помощью флага --max-old-space-size .

Помните о том, что приложение не всегда будет вести себя одинаково в том случае, если оно использует больше памяти, чем доступно в контейнере. Почему? Дело в том, что на активную память процесса (RSS) влияет множество внешних факторов, на которые не может воздействовать само приложение. Они зависят от нагруженности системы и от особенностей окружения. Например — это особенности самого приложения, уровень параллелизма в системе, особенности работы планировщика операционной системы, особенности работы сборщика мусора, и так далее. Кроме того, эти факторы, от запуска к запуску приложения, могут меняться.

У меня есть сервер Nodejs, который используется для создания около 1200 форм PDF, которые могут быть загружены клиентом позже. Они создаются с помощью pdfmake и затем выводятся в папку на сервере. Когда я выполняю код, написанный примерно в 350 документах, Nodejs не хватает памяти. Я знаю, что должен быть лучший способ сэкономить, но я не могу понять это.

Приведенный ниже метод вызывается картой массива данных из запроса Mongoose. Соответствующий код для создания и сохранения формы выглядит следующим образом:

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

Прежде чем мы перейдем к ответу, я делаю одно огромное предположение на основе информации в вопросе. Состояния вопроса create about 1200 pdf forms . Это означает, что я предполагаю, что в функции whichForm параметр certList представляет собой массив из 1200 элементов. Или я должен сказать 1200 элементов, которые будут вызывать метод createOAReport . Вы поняли идею. Я предполагаю, что проблема в том, что мы вызываем этот метод для создания PDF файлов 1200 раз внутри этого метода Array.map . Который имеет смысл, я считаю, учитывая вопрос и контекст кода.

На ответ. Основная проблема в том, что вы не просто пытаетесь создать 1200 PDF файлов. Вы пытаетесь создать 1200 PDF файлов асинхронно, что, конечно, создает нагрузку на систему, пытаясь выполнить всю эту работу одновременно. Может быть, даже больше в однопоточной системе, такой как Node.js.

Мало проблем с этим методом. В основном это не супер масштабируемый. Что если когда-нибудь у вас будет 5000 PDF файлов, которые вы хотите создать? Вам придется снова увеличить этот объем памяти. И, возможно, увеличить спецификации на машине, на которой он работает.

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

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

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

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

Тогда для вашего другого файла. Мы просто должны преобразовать его, чтобы вернуть обещание.

Я только после того, как понял этот ответ, понял, что мое первоначальное предположение неверно. Поскольку некоторые из этих PDF файлов могут быть созданы во второй функции и системе data.Flowmeters.map . Поэтому, хотя я не собираюсь демонстрировать это, вам придется применять те же идеи, которые я высказывал в этом ответе, и к этой системе. На данный момент я удалил этот раздел и просто использую первый элемент в этом массиве, так как это всего лишь пример.

Возможно, вы захотите реструктурировать свой код, как только у вас появится представление об этом, и у вас будет только одна функция, которая обрабатывает создание PDF файла, а не столько вызовов метода .map повсеместно. Абстрагируйте методы .map и держите их отдельно от процесса создания PDF. Таким образом, было бы проще ограничить количество создаваемых PDF файлов за один раз.

Также было бы неплохо добавить некоторую обработку ошибок во всех этих процессах.

ПРИМЕЧАНИЕ. Я вообще не тестировал этот код, поэтому в нем могут быть некоторые ошибки. Но общие идеи и принципы все еще применяются.


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

Это довольно распространенная проблема. Дело в том, что многие разработчики пренебрегают управлением памятью из-за неправильных представлений об ее автоматическом выделении и освобождении в современных высокоуровневых языках программирования, например JavaScript.

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

Простра н ство Интернета постоянно пополняется сложными жаргонизмами, в которых весьма непросто разобраться. Данная статья будет построена по другому принципу — просто и понятно. Вы узнаете о том, что такое утечки памяти и каковы их причины; как их легко обнаружить и диагностировать с помощью Chrome Developer Tools.

Начнем с того, что ключ к их пониманию лежит в понимании принципов управления памятью в Node.js. А это, в свою очередь, означает, что мы должны разобраться, как это управление осуществляется движком V8, используемым Node.js для JavaScript.

Вкратце напомню вам структуру памяти в V8.

Главным образом она делится на две области: стек (stack) и кучу (heap).

1.Стек — это область памяти, в которой хранятся статические данные, включающие фреймы методов/функций, примитивные значения и указатели на объекты. Он управляется ОС.

2.Куча — это самая большая область памяти, в которой V8 хранит объекты или динамические данные. Здесь же происходит сборка мусора.

Цитируя Дип К Сасидхаран, разработчика и одного из авторов книги “Развитие полного стека с JHipster”, отметим, что

“V8 управляет памятью кучи с помощью сборки мусора. Проще говоря, он освобождает память, используемую висячими объектами, т.е. объектами, на которые нет прямых или косвенных (через ссылку в другом объекте) ссылок из стека, для освобождения пространства с целью создания нового объекта.

Сборщик мусора в V8 отвечает за восстановление неиспользуемой памяти для повторного ее применения в процессе работы движка. Сборка мусора происходит по поколениям (объекты в куче распределяются по группам в зависимости от времени жизни и удаляются на разных этапах). В V8 существуют два этапа и три разных алгоритма сборки мусора”.

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

И вот мы имеем неиспользуемый фрагмент памяти. Со временем результатом накопления таких фрагментов станет ваше приложение, сигнализирующее о нехватке памяти для работы или даже ОС, требующая места для выделения памяти. А все это вместе чревато торможениями и/или выходом приложения или даже ОС из строя.

Автоматическое управление памятью, подразумевающее сборку мусора в V8, предназначено для предотвращения ее утечек. Например, циклические ссылки больше не вызывают беспокойства, но все-таки могут возникать из-за нежелательных ссылок в куче или по каким-либо другим причинам.

Рассмотрим несколько самых распространенных причин:

1.Глобальные переменные. Поскольку на них в JavaScript ссылается корневой узел (глобальный объект window или ключевое слово this ), то они никогда не подвергаются сборке мусора в течение всего жизненного цикла приложения и будут занимать память до тех пор, пока оно выполняется. И это относится ко всем объектам, на которые ссылаются глобальные переменные, а также к их потомкам. Наличие большого графа объектов со ссылками из корня может привести к утечке памяти.

2. Множественные ссылки. Ситуации, когда на один и тот же объект ссылаются несколько объектов, также могут вызвать утечку памяти при условии, что одна из ссылок становится висячей.

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

4. Таймеры и события. Использование setTimeout , setInterval , Observers и слушателей событий может вызвать утечки памяти в том случае, если ссылки на объекты кучи хранятся в их обратных вызовах без правильной обработки.

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

Сокращение использования глобальных переменных

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

Обходимся без случайных глобальных переменных

Когда вы присваиваете значение необъявленной переменной, JavaScript по умолчанию определяет ее как глобальную. Это может произойти по ошибке и привести к утечке памяти. Подобное может случиться в результате присвоения переменной к this .

В режиме strict выше приведенный пример приведет к ошибке. Однако при использовании ES модулей и компиляторов, таких как TypeScript или Babel, нет необходимости включать данный режим, так как он будет задействован по умолчанию. В последних версиях Node.js вы можете активировать режим strict глобально, сопроводив выполнение команды node флагом --use_strict .

И наконец, помните, что не следует привязывать глобальное this к функциям, использующим методы bind или call , так как это лишает использование режима strict всякого смысла.

Умеренное использование глобальной области видимости

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

  1. Минимизируйте использование глобальной области видимости. Вместо этого рассмотрите возможность применения локальной области внутри функций, так как они будут удалены в процессе сборки мусора для освобождения памяти. Если вам вынужденно приходится прибегать к использованию глобальной переменной, то задайте ей значение null , когда в ней уже не будет необходимости.
  2. Используйте глобальные переменные только для констант, кэша и переиспользуемых объектов-одиночек. Не стоит применять их в целях избежания передачи значений в коде. Для обмена данными между функциями и классами передавайте значения в качестве параметров или атрибутов объектов.
  3. Не храните крупные объекты в глобальной области видимости. Если же вам приходится это делать, то не забудьте определить их как null, когда они более не нужны. В отношении объектов кэша рекомендуется установить обработчик для периодической очистки, препятствующий их неограниченному росту.

Эффективное использование стека

Максимально возможное использование переменных стека способствует эффективной и производительной работе памяти, так как доступ к стеку происходит гораздо быстрее, чем к куче. Это также позволяет избежать случайных утечек памяти.

Конечно, использование исключительно статических данных непрактично. В реальных приложениях нам приходится работать со многими объектами и динамическими данными. Но мы можем оптимизировать применение переменных стека при помощи ряда приемов:

1.Избегайте использования ссылок переменных стека на объекты кучи по мере возможности и не храните неиспользуемые переменные;

2.Деструктуризируйте и используйте только необходимые поля объектов или массивов вместо того, чтобы целиком передавать их функциям, замыканиям, таймерам и обработчикам событий. Тогда ссылки на объекты уже не будут оставаться в замыканиях. Передаваемые поля могут быть в основном примитивами, которые будут храниться в стеке.

Эффективное использование кучи

В любом реальном приложении мы так или иначе будем использовать кучу, но с помощью следующих рекомендаций можно сделать работу с ней более эффективной:

1.По возможности копируйте объекты вместо того, чтобы передавать ссылки. Их передача возможна только в том случае, если объект крупный или операция копирования требует больших затрат.

2.По максимуму обходитесь без мутаций объекта. Вместо этого для их копирования используйте распространение объекта или Object.assign .

3.Вместо создания множественных ссылок на объект просто его скопируйте.

4.Используйте переменные с коротким жизненным циклом.

5.Старайтесь не создавать огромные деревья объектов. Если же это неизбежно, то обеспечьте им короткий жизненный цикл в локальной области видимости.

Грамотное использование замыканий, таймеров и обработчиков событий

Как уже было отмечено ранее, замыкания, таймеры и обработчики событий — это те области, где могут произойти утечки памяти. Начнем с замыканий, как наиболее часто встречающихся в коде JavaScript. Посмотрим на следующий код команды Meteor, который приводит к утечке памяти, так как переменная longStr не подлежит удалению и увеличивает объём занимаемой памяти.

Выше обозначенный код создает несколько замыканий, которые удерживают ссылки на объекты. В этом случае устранение утечки памяти возможно путем определения originalThing как null в конце выполнения функции replaceThing . Подобных ситуаций тоже можно избежать, создавая копии объектов и соблюдая выше описанный немутабельный подход.

Когда дело касается таймеров, всегда передавайте копии объектов и обходитесь без мутаций. По окончании работы таймеров проводите их очистку с помощью методов clearTimeout и clearInterval .

Тоже самое относится к слушателям событий и наблюдателям. Как только они выполнили свою задачу, очистите их. Не оставляйте слушателей в постоянно работающем состоянии, особенно если они удерживают ссылки на объекты из родительской области видимости.

В настоящее время утечки памяти в JavaScript не являются такой уж большой проблемой, как бывало раньше, благодаря эволюции движков JS и улучшениям в языке. Но если мы не будем внимательны, то они по-прежнему будут происходить и приводить к снижению производительности и сбоям в работе приложения/ОС.

Во-первых, чтобы убедиться, что наш код не приводит к утечкам памяти в приложении Node.js, нужно разобраться в принципах ее управления движком V8. Во-вторых, важно понять, что послужило их причиной.

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

Читайте также: