Пример из статьи на Хабре. Утечка памяти?

Сегодня на Хабре появилась статья, где приведен распространенный пример утечки памяти.

var theThing = null;
var replaceThing = function () {
    var priorThing = theThing;  // hold on to the prior thing
    var unused = function () {
        // 'unused' - единственное место, где используется 'priorThing',
        // но 'unused' никогда не вызывается
        if (priorThing) {
            console.log("hi");
        }
    };
    theThing = {
        longStr: new Array(1000000).join('*'),  // создаем 1Mб объект
        someMethod: function () {
            console.log(someMessage);
        }
    };
};
setInterval(replaceThing, 1000);    // вызываем 'replaceThing' каждую секунду


Честно говоря, до этого момента я думал что неплохо понимаю механизмы очистки ссылок в JS.
Не могли бы вы мне указать на ошибку в следующих моих суждениях:

var theThing = null;
var replaceThing = function () {
    // Создали локальную ссылку, если она не попадет в замыкание, то на 
    // выходе из replaceThing будет удалена (при следующей сборке мусора)
    var priorThing = theThing; 
    var unused = function () {
       // Создали локальную функцию, нигде ее не вызываем, будет удалена на выходе из replaceThing
        if (priorThing) {
            //Создали замыкание, теперь переменная останется в памяти, пока в памяти есть unused
            console.log("hi");
        }
    };
    theThing = {
       // Положили новый объект в переменную theThing, вообще ничего примечательно
        longStr: new Array(1000000).join('*'),  // создаем 1Mб объект
        someMethod: function () {
            console.log(someMessage);
        }
    };
    // Итак unused - нет ни одной внешней ссылки - недостижимый объект - удалить
    // (уходит замыкание на priorThing)
    // priorThing - нет ни одной внешней ссылки  - недостижимый объект - удалить 
};
setInterval(replaceThing, 1000);    // вызываем 'replaceThing' каждую секунду
  • Вопрос задан
  • 5045 просмотров
Решения вопроса 1
Привет, 3y3 :)

Чтобы проще было разобраться в этом примере - посмотрим вначале на более простой.
var theThing = null;

var replaceThing = function () {
  var priorThing = theThing; 
  theThing = {
    longStr: new Array(1000000).join('*'),  // создаем 1Mб объект
    someMethod: function () { 
      console.log("Hi, JS-dude!")
    }
  };
};
setInterval(replaceThing, 1000);    // вызываем 'replaceThing' каждую секунду


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

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

Если запустить этот код, то по этой логике будет утечка. В старых браузерах - обязательно будет.

Пруф:
4c8a6c47b3764be1bc65e6a8df8cfed6.png

Современные браузеры, конечно, умнее. FF и Chrome увидят, что переменная priorThing не используется и удалят её из памяти, так что старый объект благополучно умрёт.

Чтобы этого не происходило, в исходном примере сделан "финт ушами": переменная используется в некой функции unused:
var theThing = null;

var replaceThing = function () {
  var priorThing = theThing;
  ///////////////////
  var unused = function() {
    console.log(priorThing);
  };
  ///////////////////
  ...
}
setInterval(replaceThing, 1000);    // вызываем 'replaceThing' каждую секунду


Несовершенство сборщика мусора (3y3, видимо, лучшего мнения о нём) приводит к тому, что в этом случае сборщик мусора "не просекает", что переменная-то ненужная, и очистки не происходит.

Пруф Firefox:
bdd1210bf5174a13bec4d27652124e70.png

Пруф Chrome (цепочка объектов в памяти):
f1da7a209bcb4012b89468907b3df274.png

Вот, собственно, и причина.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы