Pull to refresh

Comments 91

потому что console.log держит ссылку на текст
Читаем внимательно — можно убрать консоль и проблема останется.
Если полностью убрать консоль, то скрипт выдаст ошибку. Вы просто закрыли отображение консоли…
Не в том ли проблема, что каждый раз создается новый объект?
На него нет ссылок. Он должен быть вычищен гарбаж-коллектором. Или я не прав в этом?
вот этим xhr.onreadystatechange вы создаете замыкакание, которое имеет доступ к объявленным переменным, поэтому gc оставляет в памяти переменную xhr
Насколько я понял, Олег уже пробовал обнулять xhr.onreadystatechange в обработчике события. Не помогло.
Обнуляя я порушил ссылку, но в области видимости функции остался XHR.
Да, интересная мысль — сам факт обнуления ссылки создал новую ссылку через замыкание. Ну тогда надо еще xhr = null;
Не, не создал. Она уже была. Внутренняя анонимная функция видела XHR уже в момент рождения (хоть и не использовала его явно). И каждая новая видела свой XHR.
Честно говоря, мне казалось, что в замыкание попадают только те переменные, которые ты явно используешь внутри функции. А то так получится, что ты одной анонимной функцией сходу замыкаешь все переменные во всех доступных сейчас диапазонах видимости.

Это, наверное, легко проверить, если выполнить eval() внутри функции. Будут видны те переменные, на которые не ссылаешься в коде — значит все.
Совершенно не однозначно. Браузеры могут отключать оптимизации когда видят eval
Тоже резонно. Но я все же думаю, что это слишком необычная ситуация, чтобы браузеры были под нее заточены.

Просто предполагаю.
Евал выполняется очень редко, но когда он выполняется — он должен выполнится корректно. А вот код без евала выполняется часто, потому может быть соптимизирован.
Правильно. И самое прикольное, что мы никак не можем этого проверить. :)
Исходники Хромиума и Фокса — открыты, можно обпроверяться)))
Это несколько экстремально. :)

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

Если замыкание держит ссылку на неиспользуемый объект, то GC их не сможет освободить. Если же не держит, то сможет.
я как то сталкивался с тем что при отладке кода в хроме внутри замыкания те переменные которые не используются не были доступны в отладчике, так что можно предположить то что в каких то случаях он не тащит их повсюду :)
По стандарту JavaScript все переменные попадают в замыкание.
По стандарту они должны попадать, но что, в теории, мешает браузеру соптимизировать переменные, которые точно не будут использоваться? При условии, что поведение будет совпадать со стандартным.
Ничего не мешает :)
Но я бы на это полагаться не стал — в том же IE циклические ссылки (элемент-эвент-замыкание на элемент) GC научился собирать только в 9 версии.
Я кстати подозреваю, что именно для этого в стандарте написано, что только прямой вызов eval выполняется в контексте функции (косвенные — в глобальном контексте) — так оптимизировать намного проще.
Ссылок то нет, но сообщения обрабатываются, так что здесь вероятно неопределенное поведение и поэтому не удаляет. Ведь в сущности таймаут может быть и с 10 секунд, откуда GC знать когда удалять объект.
Запустил тест. А разве при каждом запуске старый инстанс XHR не перетрется новым? И таким образом на него потеряется ссылка?
не перетрётся. замыкание-то новое. а ответ оказывается на поверхности был…
UFO landed and left these words here
А разве сама функция, указанная в setInterval не замыкается на контекст, в котором был вызван setInterval? Ведь, по идее, при каждом вызове setInterval будет создаваться новая функция. Я могу ошибаться, и если это так, то хотелось бы знать в чем.
При каждом срабатывании интервала будет вызвана одна и та же функция, она не пересоздается заново.
пруф чего?
Вы имеете основания полагать, что в этом коде:

setInterval(function(){

}, 10);

анонимная функция будет создаваться каждый раз новая?
Хм, я думал, что речь идет о внутренней функции (которая xhr.onreadystatechange = function() ...)
При каждом срабатывании интервала будет вызвана одна и та же функция, она не пересоздается заново.
Вот тут функция тоже только один раз объявлена:
function factory(x) {
   return (function(y){
      return x + y;
   });
}

но каждый вызов factory будет создавать новую функцию.
Пример другой, просто более очевидный.
Это я понимаю. Я к тому, что функция, выполняемая с интервалом — он одна, как фабрика у вас.
xhr.onreadystatechange = function() {
if(this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
delete xhr;
}

?
Переменные удалять нельзя ;-)

Обычно делают xhr = null; в таких случаях.
А почему нельзя удалять? Какая разница, сделать ее null или undefined?
В данном случае между null и undefined принципиальной разницы нет.
var xyz = function(){
console.log('_xyz_');
delete xyz;
}

xyz();
xyz();

Это сработало как я и ожидал:
_xyz_
ReferenceError: xxxyyyzzz is not defined


BTW. На хабре скрипты что-то в консоль пишут :)
Вы это пробовали в FireBug/консоли или скриптом на нормальной странице?
Потому что в первом случае будет работать как вы ожидали (потому что выполняется через eval, а переменные объявленные в eval могут удаляться), а вот во втором — никакого удаления не произойдет (_xyz_ будет записано два раза).
Удалять нельзя не по каким-то там guidlines, а потому что в стандарте так написано — переменные помечаются аттрибутом {DontDelete}.

И еще обратите внимание на такой ньюанс:
var a = 10;
b = 20;
delete a;
delete b;
console.log(a); // 10
console.log(b); // ReferenceError: b is not defined

Так происходит потому, что b — не переменная, а свойство глобального объекта.
Спасибо за разъяснения! Что называется, RTFM :)
Я так понимаю, это лечится через
var obj = {a:10};
delete obj.a;

Но вообще надо пробовать.
Так конечно да, в данном случае вы удаляете не переменную, а свойство объекта.
UFO landed and left these words here
Не все задачи, которые имеются, можно так решить. Как обойти я примерно представляю, мне хочется понять что я не понимаю в работе GC и работе именно вышеуказанного куска.
Всякие там good practices подсказывают, что попробовать надо так:
var callback = function() {
  if(this.readyState == 4 && this.status == 200) {
    console.log(this.responseText);
  }
};

setInterval(function(){
     var xhr = new XMLHttpRequest();
     xhr.open('GET', 'json.txt', true);
     xhr.onreadystatechange = callback;
     xhr.send('');
}, 500);
Только хотел предложить подобное решение. Автор, попробуйте, поможет ли это?
Запустил пробовать. Рядом запустил аналог с простым jQ.get
var cb = function(d){ var a = 2 + 3; };
setInterval(function(){
	$.get('f.txt', cb);
}, 500);
В общем суяд по всему оба варианта текут ГОРАЗДО меньше чем исходный (оба, тот который выше и jQuery.get). Но все-же со временем размер процесса немного пухнет.

Процесс «без jQuery» за первые 40 минут распух с 11 до 21Мб но за следующие 1 час 20 минут не увеличился. Процесс «c jQuery» аналогично — с 8 до 10 и держится.
Судя по всему корень зла — повторно создающееся замыкание с видимостью XHR.
Тоесть мы минимизируем то, что попало в замыкание к колбэку и попутно вывели инстанс XHR из его области видимости?
Каждый раз не создаётся функция колбека. Да и замыкания не происходит, так что память должна нормально очищаться.
Имхо, проще так:
setInterval(function(){
     var xhr = new XMLHttpRequest();
     xhr.open('GET', 'json.txt', true);
     xhr.onreadystatechange = function() {
        if(this.readyState == 4 && this.status == 200) {
           console.log(this.responseText);
        }
     };
     xhr.send('');
     xhr = null;
}, 500);
А посмотреть в тест топика пробовали?
Такой подход практически ничем не отличается от приведенного выше (с callback функцией).
Простите, сам невнимательно прочитал, незаметил xhr = null.
Похоже, что Google Chrome где-то не удаляет Persistent указатель… вообще очень много в интернете статей про утечки памяти при работе с DOM элементами (они тоже хранятся как Persistent handle)…
В голову почму-то пришла мысль о защите от фрагментации памяти, но походу это бред. Попробуйте использовать jQeury и посмотреть результат. Ждем mraleph в треде, думаю он пояснит в чем тут дело.
Вообще есть подозрение что ликает не JS GC, а реализация объекта XMLHttpRequest.
Посмотрите, плиз, течёт ли следующий код?

xhr = new XMLHttpRequest();
xhr.onreadystatechange = callback;

function callback() {
	if(xhr.readyState == 4 && xhr.status == 200) {
	   console.log(xhr.responseText);
	}
};

function request() {
	xhr.open('GET', 'json.txt', true);
	xhr.send(null);
}

setInterval(request, 500);
А в чем отличие от ранее предложенного выноса колбэка из замывания?
Пардон, AndreyF уже постил этот код выше. Тут используется только один экземпляр XMLHttpRequest.
А если просто запустить страницу, но не запускать Девелопер-тулз, и смотреть утечку по диспетчеру задач, то есть течь или нет?
И так делаю, правда смотрю по about:memory
Проверьте не по about:memory, потому что может оказаться так, что сбор статистики об использовании памяти как раз и есть эта «течь».
Это как в квантовой физике прям )
Ничего военного. Сбор статистики делает сам браузер. Он должен где-то хранить результаты?
Я про то, что факт наблюдения влияет на результат.
Ну так я с этим и не спорю. Квантовые явления в макромире! :)
есть. он это уже написал. (минус не мой, если чo ^^)
Даешь ручное управление памятью в js! :)

Или быть может мне подскажет, как бы выполнитить профайлинг жаваскриптового приложения в браузере?
В Хроме есть профайлер отличный.
Хотя там в первую очередь по времени выполнения, а вот по утечкам памяти — было бы вообще круто.
Насколько я помню он не очень отличается от firebug-овского?

А вообще хотелось бы иметь возможность генерировать профайл-лог, для его последующего анализа тем-же kcachegrind
Я под лисой делал одну оптимизацию для проекта, т.к. память лилась. Допустим что браузер не очень корректно может почистить XMLHttpRequest обьекты. Можно попробовать минимизировать утечки. Решений было несколько.

1. Пул XMLHttpRequest обьектов. Не создавать каждый раз новый, а брать старый из пула если он помечен как свободный.

2. А вы в курсе что происходит с вашими данными и контентом после onComplete? Допустим вы загрузили json файл размером в мегабайт. Так вот этот мегабайт текста будет висеть в памяти внутри XMLHttpRequest обьекта (переменная responseText). Обьект можно очистить, вызвав функцию abort() на нем. Причем ее я вызваю уже после того как совершил все обработки и т.д. onComplete. Собственно это и часть функционала пула, когда ажакс помечен как свободный — делать ему аборт, очищать калбяки и прочие присоски и щупальца :).

3. Ну и понятно что надо следить при таком пуле чуть тщательней за обьектами. Например создал обьект, вызвал, а потом пытаешься вызвать еще раз, а он уже очищен и обнулен. Для таких особых случаев у меня есть флаг, типа не очищать.
А что если так?

var xhr = new XMLHttpRequest();
xhr.open('GET', 'json.txt', true);
xhr.onreadystatechange = function() {
    if(this.readyState == 4 && this.status == 200) {
        console.log(this.responseText);
    }
};

setInterval(function(){
     xhr.send('');
}, 500);

XMLHttpRequest вполне себе reuseble, не обязательно его создавать каждый раз. Повторный вызов send на незаконченный запрос, убьет (сделает abort) незаконченный.
Странно, но никто не заикнулся про использование setTimeout, вместо setInterval.
Sign up to leave a comment.

Articles