Как предотвратить утечки памяти в node.js ?

#memory-leaks #node.js

#утечки памяти #node.js

Вопрос:

Мы знаем node.js предоставляет нам огромную власть, но с большой властью приходит большая ответственность.

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

РЕДАКТИРОВАТЬ: Извините за мое невежество, в V8 есть мощный сборщик мусора.

Комментарии:

1. Подожди, ват? Реализация JS (или, в более общем плане, любая реализация языка, где ручное управление памятью находится вне компетенции программистов) без GC кажется мне довольно бесполезной. И на самом деле, Google показал мне code.google.com/apis/v8/design.html#garb_coll как самый первый результат. Откуда у вас идея «V8 не выполняет GC»?

2. В V8 есть эфемерный и линейный сборщик мусора, который останавливает мир при его очистке. Подразумевать, что у него нет GC, — это бессмыслица. На самом деле, это одна из лучших JS GCS, которые у нас есть. Еще одна замечательная функция есть в IE9 . Я слышал, что Mozilla собирается улучшить свой дизайн GC в будущем, в направлении версии 8.

Ответ №1:

Насколько я знаю, движок V8 не выполняет сборку мусора.

V8 имеет мощный и интеллектуальный сборщик мусора в сборке.

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

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

Способ заставить GC собирать данные — это обнулить ваши переменные.

 function(foo, cb) {
    var bigObject = new BigObject();
    doFoo(foo).on("change", function(e) {
         if (e.type === bigObject.type) {
              cb();
              // bigObject = null;
         }
    });
}
  

Как v8 узнает, безопасно ли собирать большой объект в мусор, когда он находится в обработчике событий? Это не так, поэтому вам нужно сообщить ему, что он больше не используется, установив переменной значение null.

Различные статьи для чтения:

Комментарии:

1. Циклические ссылки не должны представлять ни малейшей проблемы для «интеллектуального» GC. Даже самые простые GCS могут справиться с ними (пересчет не является реальным GC). Хотя подсказка об обработчиках событий кажется правильной.

2. Необходимо ли обнулять «BigObject» внутри области видимости функции. Разве GC не должен позаботиться об этом после завершения функции?

3. @jwerre эта функция никогда не завершается. Это прослушиватель изменений. Эта функция будет завершена только в том случае, если отправитель события, возвращенный doFoo(foo) сборщиком мусора get.

4. Это действительно хорошо. Райнос сказал, что существуют различные другие способы создания подобных проблем; у кого-нибудь есть хорошее руководство по другим вещам, на которые следует обратить внимание?

5. Установка @Raynos bigObject на null , на мой взгляд, не имеет никакого смысла в этой ситуации. Удаление самого обработчика событий позволит bigObject собирать данные, а это то, чего мы хотим. Однако только нулевая bigObject ссылка просто вызовет ошибку времени выполнения при следующем вызове обработчика.

Ответ №2:

Я хотел убедить себя в принятом ответе, в частности:

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

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

Если у вас watch -n 0.2 'ps -o rss $(pgrep node)' запущен другой терминал, вы можете наблюдать за происходящей утечкой. Обратите внимание, как комментирование в buffer = null или с помощью nextTick позволит завершить процесс:

 (function () {
    "use strict";

    var fs = require('fs'),
        iterations = 0,

        work = function (callback) {
            var buffer = '',
                i;

            console.log('Work '   iterations);

            for (i = 0; i < 50; i  = 1) {
                buffer  = fs.readFileSync('/usr/share/dict/words');
            }

            iterations  = 1;
            if (iterations < 100) {
                // buffer = null;

                // process.nextTick(function () {
                    work(callback);
                // });
            } else {
                callback();
            }
        };

    work(function () {
        console.log('Done');
    });

}());
  

Комментарии:

1. Я тоже склонен учиться, понимать и принимать вещи лучше всего, когда я могу их как-то визуализировать; это был хороший пример, который позволил мне сделать именно это — увидеть изменения в памяти при выполнении GC. Спасибо, что поделились.

2. @dukedave Я не уверен, что это хороший пример. Очевидно, что локальная buffer переменная не может собирать мусор до завершения вызова рекурсивной функции, если вы явно не обнуляете ее, если вы явно не обнуляете ее, но действительно ли такое поведение можно считать утечкой? Когда рекурсивный вызов завершится, все локальные переменные, естественно, будут иметь право на GC, независимо от того, обнуляете вы buffer или нет. Нет?

3. @plalx Я бы счел это утечкой, поскольку это касается прикладного программиста (т. Е. Они не понимали, что это buffer будет расти). Возможно, мне следовало более четко указать, что это касалось утверждения принятого ответа о том, что «Ваша главная проблема заключается в непонимании того, как замыкания поддерживают ссылку на область видимости и контекст внешних функций»; Я обновлю свой ответ, чтобы отразить это сейчас.

4. отличный ответ на простом примере!

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

Ответ №3:

активная сборка мусора с:

 node --expose-gc test.js
  

и использовать с:

 global.gc();
  

Удачного кодирования 🙂

Комментарии:

1. Ручной вызов сборщика мусора не поможет при реальной утечке памяти. Среда выполнения в любом случае периодически вызывает сборщик мусора, и утечка памяти на языке GCed вызвана созданием ссылок, которые сборщик мусора не может безопасно собрать. Однако при отладке частый вызов gc может значительно увеличить отношение сигнал / шум и значительно упростить определение реальной утечки памяти.