Утечка памяти в 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 нет динамического управления памятью, этот материал полностью выходит за пределы вашего диапазона.
Ответ 2
Закрытия часто упоминаются при разговоре об утечке памяти в JS. Пример здесь:
http://www.javascriptkit.com/javatutors/closuresleak/index2.shtml
Ответ 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.