Утечка памяти в JavaScript: каковы они, как их определить, как их создавать

Я только что помогал с некоторыми интервью для нового разработчика, а JavaScript - большая часть моей роли и той роли, которую мы собираем. Честно говоря, кандидат не был так хорош, и он не очень разбирался в JavaScript, однако в интервью он перепутал JavaScript с С# и начал обсуждать утечки памяти в JS. Я хотел вмешаться, однако именно в этот момент я понял, насколько мало я знаю об утечках памяти в JS, кроме того, что они используют много памяти и замедляют работу.

Когда вы думаете об этом во время интервью, единственное, что я помню, это OReilly Def Guide (думаю, это было четвертое издание), в котором упоминаются Mark и Sweep Garbage Collections. Но с тех пор, как я читал это, это замирает, и я не могу этого расширять. Я нашел очень мало на эту тему, что ясное и краткие (кроме статьи Крокфорда, которая не была такой ясной).

Может кто-то, пожалуйста, подытоживать как можно более просто: что такое утечки памяти в JS, как мы можем их обнаружить, как их создать? Я писал JS годами, и это полностью сбило мои знания и уверенность, поскольку я Я никогда не думал об этом!

Ответ 1

На самом деле, "настоящая" утечка памяти никогда не должна быть возможной на языке, который имеет автоматический сборщик мусора. Таким образом, если есть утечка памяти, всегда она является ошибкой в ​​подстилающем движке (например, проблема named function expressions в некоторых IE).

Итак, после того, как мы разъяснили это, все еще можно получить много памяти с помощью javascript и удерживать ее, не выпуская. Но это не настоящая утечка памяти. Например, каждый function call создает закрытие в ECMAscript. Лексическое замыкание, среди прочего, копирует ссылку на каждый родительский контекстный файл (объекты активации и переменные). Таким образом, это требует некоторой памяти, особенно если вы создаете много замыканий.

Другой пример из мира DOM Javascript: мы создаем динамическое изображение с помощью new Image() и устанавливаем источник в большое изображение. Теперь у нас есть ссылка на изображение, и он не может собрать мусор, пока все ссылки не исчезнут или не будут использованы (даже если хороший инструмент памяти правильно скажет вам, что память использовалась для изображений, а не для javascript).

Но на самом деле это единственные сценарии, где вы действительно можете "утешить" память на этом языке. Опять же, это не утечка памяти, как C malloc(), где вы снова забудете free() этот раздел. Поскольку в ECMAscript нет динамического управления памятью, этот материал полностью выходит за пределы вашего диапазона.

Ответ 3

var trolls = (function () {
  var reallyBigObject = eatMemory();

  // make closure (#1)
  // store reallyBigObject in closure
  (function () {
    var lulz = reallyBigObject;
  })();

  // make another closure (#2)
  return function () {
    return 42;
  };
})();

Вы ожидали бы, что trolls будет просто function () { return 42; }, и вы ожидаете, что действительноBigObject будет удален и собран мусор.

Это не так, потому что если одно замыкание (# 1) ссылается на переменную во внешней области. Тогда все замыкания (# 2) ссылаются на эту переменную.

Просто потому, что у вас есть ссылка на # 2, значит, у вас есть ссылка на reallyBigObject, которая не будет очищена до тех пор, пока не будет # 2.

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

Обратите внимание, что вышеуказанные данные относятся к v8. Любой полностью совместимый с ES5 браузер будет протекать с помощью

var trolls = (function () {
  var reallyBigObject = eatMemory();
  return function () {};
})();

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

Ответ 4

Javascript реализован по-разному во всех браузерах. Но есть стандарт, которому должны следовать все браузеры: ECMAscript.

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