Утечки и закрытия памяти в JavaScript - когда и почему?

Вы часто читаете в Интернете, что использование закрытий - это массивный источник утечек памяти в JavaScript. В большинстве случаев эти статьи относятся к смешиванию script кода и событий DOM, где script указывает на DOM и наоборот.

Я понимаю, что закрытие может быть проблемой.

Но как насчет Node.js? Здесь мы, естественно, не имеем DOM, поэтому нет возможности иметь побочные эффекты утечки памяти, как в браузерах.

Какие еще могут быть проблемы с закрытием? Может ли кто-нибудь разработать или указать мне хороший учебник по этому поводу?

Обратите внимание, что этот вопрос явно нацелен на Node.js, а не на браузер.

Ответ 1

Этот вопрос спрашивает о чем-то похожем. По сути, идея заключается в том, что если вы используете замыкание в обратном вызове, вы должны "отписаться" от обратного вызова, когда вы закончите, чтобы GC знал, что он не может быть вызван снова. Это имеет смысл для меня; если у вас есть замыкание, ожидающее вызова, GC будет трудно узнать, что вы закончили с ним. Удаляя замыкание вручную из механизма обратного вызова, оно становится не связанным и доступным для сбора.

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

Вот краткий отрывок из статьи о Mozilla:

  • Jimb Essers node-mtrace, который использует утилиту GCC mtrace для профилирования использования кучи.
  • Dave Pachecos node-heap-dump делает снимок кучи V8 и сериализует все это в огромном файле JSON. Он включает в себя инструменты для просмотра и исследования полученного снимка в JavaScript.
  • Danny Coatess v8-profiler и node-inspector предоставляют привязки Node для профилировщика V8 и интерфейс отладки Node с помощью WebKit Web Inspector.
  • Феликс Gnasss из той же вилки, которая отключает граф фиксаторов
  • Учебное пособие по утечке памяти узла Felix Geisendörfers представляет собой краткое и приятное объяснение того, как использовать v8-profiler и node-debugger, и в настоящее время является современным для отладки большинства утечек памяти в Node.js.
  • Платформа Joyents SmartOS, которая предоставляет в ваше распоряжение арсенал инструментов для устранения утечек памяти в Node.js.

Ответы на этот вопрос в основном говорят о том, что вы можете помочь GC, назначив null переменным замыкания.

var closureVar = {};
doWork(function callback() {
  var data = closureVar.usefulData;
  // Do a bunch of work
  closureVar = null;
});

Любые переменные, объявленные внутри функции, исчезнут, когда функция вернется, за исключением тех, которые используются в других замыканиях. В этом примере closureVar должен находиться в памяти до callback(), но кто знает, когда это произойдет? После того, как обратный вызов был вызван, вы можете дать подсказку GC, установив для вашей переменной замыкания значение null.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Как вы можете видеть из комментариев ниже, есть некоторые пользователи SO, которые говорят, что эта информация устарела и несущественна для Node.js. У меня пока нет окончательного ответа на этот вопрос; Я просто публикую то, что нашел в сети.

Ответ 2

Вы можете найти хороший пример и объяснение в этом блоге Дэвида Глассера.

Ну, вот оно (я добавил несколько комментариев):

var theThing = null;
var cnt = 0; // helps us to differentiate the leaked objects in the debugger
var replaceThing = function () {
    var originalThing = theThing;
    var unused = function () {
        if (originalThing) // originalThing is used in the closure and hence ends up in the lexical environment shared by all closures in that scope
            console.log("hi");
    };
    // originalThing = null; // <- nulling originalThing here tells V8 gc to collect it 
    theThing = {
        longStr: (++cnt) + '_' + (new Array(1000000).join('*')),
        someMethod: function () { // if not nulled, original thing is now attached to someMethod -> <function scope> -> Closure
            console.log(someMessage);
        }
    };
};
setInterval(replaceThing, 1000);

Пожалуйста, попробуйте с нулевым и не обнуляющим originalThing в Chrome Dev Tools (вкладка шкалы времени, просмотр памяти, нажмите запись) Обратите внимание, что приведенный выше пример относится к браузеру и средам Node.js.

Благодарю также и особенно Вячеслава Егорова.

Ответ 3

Я должен не согласиться с закрытием, являющимся причиной утечек памяти. Это может быть справедливо для более старых версий IE из-за его дрянной коллекции мусора. Пожалуйста, прочитайте эту статью от Дугласа Крокфорда, в которой четко указано, что происходит с утечкой памяти.

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

Утечки не проблема, эффективная сборка мусора. Утечки могут происходить как в браузере, так и в серверных JavaScript-приложениях. Возьмите V8 в качестве примера. В браузере сбор мусора происходит на вкладке при переключении на другое окно/вкладку. Утечка подключается при работе на холостом ходу. Вкладки могут находиться в режиме ожидания.

На сервере все не так просто. Утечки могут произойти, но GC не так рентабельен. Серверы не могут позволить себе GC часто, или это повлияет на производительность. Когда процесс node достигает определенного использования памяти, он запускается в GC. Затем утечки будут периодически удаляться. Но утечки все еще могут происходить быстрее, что приводит к сбоям в работе программ.