Сбор мусора с node.js

Мне было любопытно, как шаблон вложенных функций node.js работает с сборщиком мусора в v8. вот простой пример

readfile("blah", function(str) {
   var val = getvaluefromstr(str);
   function restofprogram(val2) { ... } (val)
})

если restofprogram работает долго, не означает ли это, что str никогда не будет собирать мусор? Насколько я понимаю, с узлом у вас много вложенных функций. Получает ли это мусор, если restofprogram был объявлен снаружи, поэтому str не может быть в области видимости? Это рекомендуемая практика?

РЕДАКТИРОВАТЬ Я не собирался усложнять проблему. Это была просто небрежность, поэтому я изменил это.

Ответ 1

Простой ответ: если значение str не ссылается нигде (и str сам по себе не ссылается на restofprogram), он станет недоступным, как только вернет function (str) { ... }.

Подробности: компилятор V8 отличает реальные локальные переменные от так называемых переменных контекста, захваченных закрытием, затененных с помощью с -statement или вызовом eval.

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

Контекстные переменные живут в структуре контекста, выделенной кучей. Они исчезают, когда структура контекста умирает. Важно отметить, что переменные контекста из одной и той же области присутствуют в структуре той же. Позвольте мне проиллюстрировать его примером кода:

function outer () {
  var x; // real local variable
  var y; // context variable, referenced by inner1
  var z; // context variable, referenced by inner2

  function inner1 () {
    // references context 
    use(y);
  }

  function inner2 () {
    // references context 
    use(z);
  }

  function inner3 () { /* I am empty but I still capture context implicitly */ } 

  return [inner1, inner2, inner3];
}

В этом примере переменная x исчезнет, ​​как только outer вернется, но переменные y и z исчезнут только тогда, когда и inner1, inner2 и inner3 умирают. Это происходит потому, что y и z выделяются в одной и той же структуре контекста, и все три закрытия неявно ссылаются на эту структуру контекста (даже inner3, которая не использует его явно).

Ситуация становится еще более сложной, когда вы начинаете использовать с -statement, try/catch -statement, который на V8 содержит неявный с - оператор внутри catch или глобальный eval.

function complication () {
  var x; // context variable

  function inner () { /* I am empty but I still capture context implicitly */ }

  try { } catch (e) { /* contains implicit with-statement */ }

  return inner;
}

В этом примере x исчезает только тогда, когда inner умирает. Потому что:

  • try/catch - содержит неявное с -statement в catch catch
  • V8 предполагает, что любое с -statement затеняет все локали.

Это заставляет x стать контекстной переменной, а inner фиксирует контекст, поэтому x существует до inner.

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

Ответ 2

На самом деле ваш пример несколько сложный. Это было специально? Вы, кажется, маскируете внешнюю переменную val с внутренним лексически измененным аргументом restofprogram() val, вместо того, чтобы фактически использовать ее. Но в любом случае вы спрашиваете о str, поэтому позвольте мне проигнорировать хитрость val в вашем примере только ради простоты.

Я предполагаю, что переменная str не будет собрана до завершения функции restofprogram(), даже если она ее не использует. Если команда restofprogram() не использует str и, она не использует eval() и new Function(), тогда она может надежно собраны, но я сомневаюсь. Это была бы сложная оптимизация для V8, вероятно, не стоит проблем. Если в языке не было eval и new Function(), это было бы намного проще.

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

Теперь я задаюсь вопросом, не означает ли вы что-то еще, чем то, что вы на самом деле писали в своем примере. Вся программа в Node похожа на браузер - она ​​просто регистрирует обратные вызовы событий, которые запускаются асинхронно позже, после того как основной объект программы уже завершен. Кроме того, ни один из обработчиков не блокирует, поэтому никакая функция не выполняет какое-либо заметное время для завершения. Я не уверен, понял ли я то, что вы на самом деле имели в виду, но я надеюсь, что то, что я написал, будет полезно для понимания того, как все это работает.

Обновление:

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

Если ваша программа выглядит примерно так:

readfile("blah", function (str) {
  var val = getvaluefromstr(str);
  // do something with val
  Server.start(function (request) {
    // do something
  });
});

Затем вы также можете записать его следующим образом:

readfile("blah", function (str) {
  var val = getvaluefromstr(str);
  // do something with val
  Server.start(serverCallback);
});
function serverCallback(request) {
  // do something
});

Это приведет к тому, что str выйдет из области действия после вызова Server.start() и в конечном итоге будет собрано. Кроме того, это сделает ваш отступ более управляемым, что нельзя недооценивать для более сложных программ.

Что касается val, вы можете сделать его глобальной переменной в этом случае, что значительно упростит ваш код. Конечно, вам это не нужно, вы можете бороться с закрытием, но в этом случае сделать val глобальным или сделать его живым во внешней области, как для обратного вызова readfile, так и для функции serverCallback, кажется самым простым решением.

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

Ответ 3

Я предполагаю, что str не будет собираться мусором, потому что он может использоваться restofprogram(). Да, и str должна получить GCed, если restofprogram был объявлен за пределами, за исключением, если вы выполните что-то вроде этого:

function restofprogram(val) { ... }

readfile("blah", function(str) {
  var val = getvaluefromstr(str);
  restofprogram(val, str);
});

Или, если getvaluefromstr объявлен как что-то вроде этого:

function getvaluefromstr(str) {
  return {
    orig: str, 
    some_funky_stuff: 23
  };
}

Вопрос о последующих действиях: делает ли v8 просто "гладкий" GC или он делает комбинацию GC и ref. подсчет (например, python?)