5 апреля 2012 в 20:00

Утечки памяти в IE8, или страшная сказка со счастливым концом из песочницы



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

Однажды в одном большом-большом городе, в одной большой-большой ИТ-компании тестировали один большой-большой проект в одном очень используемом браузере. И обнаружили там утечки памяти. Большие-большие. Прям незадолго до релиза.

И было бы это неудивительно, если бы разработчики были совсем глупые. Но нет же, разработчики наизусть знали «Understanding and Solving Internet Explorer Leak Patterns». Циклические ссылки разрывали, замыкания не использовали, к событиям относились с должным почтением и удалять обработчики не забывали. Да вот только от утечек это не спасло.

Discalimer: все упомянутые ниже сущности, события и фрагменты кода являются художественным вымыслом. Все совпадения случайны.

Ну что было делать добрым молодцам? Засели они очередной раз причесать свои компоненты и посмотреть, не пропустили ли чего, стали запускать программки разные, да пытать ответа у Гугля. Но не помогли программки, молчал и мудрый Гугол. Пришлось засучить рукава и по строчке выискивать, где затаилась зараза. И нашлась зловредная строчка, и выглядела она примерно так:

var cell = tableEl.firstChild.rows[0].cells[0];

И пришли разработчики в недоумение, пришлось им заняться исследованием проблемы и её решением. С чем они и управились.

А дальше сказка кончается и начинается код демонстрирующий проблему:

<!DOCTYPE HTML>
<html>
<body>
<span id="count">0</span> \<input id="num" value="1000" /\>
<input type="button" value="GO!" onclick="execute()" />
<hr />
<div id="test"></div>
</body>

<script type="text/javascript">
var count = 0;
function execute()
{
var val = document.getElementById('num').value;
for (var i = 0; i < val; i++)
{
var domEl = document.getElementById('test');
domEl.innerHTML = '<table><tbody><tr><td>A1</td></tr><tr><td>B1</td></tr></tbody></table>';
domEl.firstChild.insertRow(0);
domEl.removeChild(domEl.firstChild);
count++;
}
document.getElementById('count').innerHTML = count;
};
</script>
</html>


Запускаем Process Explorer и несколько раз нажимаем «GO!» — убегает по 10 мегабайт с нажатия:

image

Утечка скрывается в строчке domEl.firstChild.rows[0] в чем можно убедиться, просто её закомментировав. Причем проявляется баг только в режиме IE8 Standards, режимы IE7 и IE8 Quirks данному заболеванию не подвержены.

Замена на domEl.firstChild.firstChild или на getElementById помогает устранить утечку, но если таких строчек сотни по всему проекту? Может быть, есть менее трудоемкий вариант?

Попробуем прикинуть, как индусы из майкрософт сумели достичь такого эффекта. Очевидно, коллекции столбцов и ячеек создаются только когда используются, решение вполне здравое. А когда таблицу отцепляют от DOM дерева документа, их не уничтожают — тоже вполне понятно и разумно. Только вот в сборщике мусора почему-то забыли учесть образующиеся при этом циклические ссылки.
Гипотезу можно проверить. Если дело в создании коллекций, утечки будут и в других случаях, когда используются индексы. Заменим rows[0] на insertRow(0). Бинго! Утечка сохранилась.

Теперь ясен и способ борьбы. Раз дело в циклических ссылках между DOM элементами TBODY и TD, значит, нужно просто удалить всех потомков TBODY:

while(domEl.firstChild.firstChild) {
while(domEl.firstChild.firstChild.firstChild)
domEl.firstChild.firstChild.removeChild(domEl.firstChild.firstChild.firstChild);
domEl.firstChild.removeChild(domEl.firstChild.firstChild);
}

Сработало, но этого всё ещё недостаточно для счастья, заботиться отдельно о правильном удалении таблицы в компоненте достаточно муторно, да и забыть что-нибудь недолго. Можно, конечно, автоматизировать процесс, использовать что-то вроде getElementsByTag('TABLE')… Но не нужно, так как есть волшебное свойство innerHTML, которое все сделает за нас:

Заменяем domEl.removeChild(domEl.firstChild) на domEl.innerHTML = ''
Проверяем. Работает.

Подводим итог:

Данный тип утечки не документирован, возникает только в режиме IE8 Standards при использовании методов и свойств объектов DOM таблицы, использующих индексы строк или столбцов. (.rows[], .cells[], insertRow(), и т.д.)

Лучший вариант обхода проблемы — не использовать данные методы и свойства.

Альтернативный — следить за тем, чтобы DOM элементы всех строк таблицы были отсоединены от своего предка, либо использовать самый простой вариант: innerHTML = ''
@Aniro
карма
13,0
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (36)

  • +2
    Лучший вариант обхода проблемы — не использовать данные методы и свойства.

    Альтернативный — следить за тем, чтобы DOM элементы всех строк таблицы были отсоединены от своего предка, либо использовать самый простой вариант: innerHTML = ''

    А идеальный вариант — не поддерживать устаревшие браузеры, предлагая пользователю на выбор варианты:
    1. ругаться на «глючный ие» или
    2. установить нормальный браузер.
    • +6
      IE8 не такой уж и старичок :)
      С пунктом два полностью согласен.
      • +20
        При разработке веб-приложений для корпоративных клиентов пункт 2 можно даже не обсуждать. Тем более, что как уже сказали ниже, для XP ничего свежее восьмерки нет. Продукт не поддерживающий IE8 с великими шансами просто не будет продаваться. Sad but true.
        • +2
          В одной чудесной, ооооооооооооочень большой, всем известной зеленой компании до сих пор используют и IE6, и для меня ужас, т.к. я один из тех, кто делает фронтенд для ее корпоративного портала. «Благо» они потихоньку переходят на IE8.
          • 0
            Перейдут на IE9 и вообще проблем не будет. В MS тоже умные ребята
            • 0
              Жалеют баблоса вот и все
    • 0
      Скорее всего пользователи не властны выбрать другой браузе. В корпоративной среде ИЕ — стандарт.
    • +3
      IE8 — самая последняя версия для XP (которая еще используется)
      Хотя попросить установить нормальный браузер — и впрямь идеальный вариант:)
      • –8
        А заодно и нормальную ОСь ;)
        • –5
          Коллеги, которые минусуют, я ведь ничего не имею против других ОС, особенно тех, которые написаны ногой, тут личное дело каждого…
          • +3
            Тут дело в том что надоело впихивание этой фразы в любом посте где есть упоминание винды.

            А ещё часто со сменой Оси надо переучивать\доучивать пользователей, а в больших компаниях это лишние расходы. Оно им надо?
            • +5
              Быть может имеется ввиду Win7? Ведь это тоже нормальная ОСь :)
            • –1
              Вот и достается конечным пользователям вечный треш
            • 0
              Я сам на винде сижу и все в компании кроме разработчиков на винде. Переучиваться и правда сложно, а инфраструктуру менять еще и дорого
          • 0
            Какой админ, такая и ось…
  • +1
    Спасибо! Положим в копилку знаний.

    Мне вчера тоже ИЕ нервы помотал:
    Открывал дочернее окно с приложением и передавал в него массив данных.
    В дочернем окне никак не удавалось обработать эти данные.
    Часа 3 ломал голову и потом только догадался, что прототип массива данных (так же как и сам массив) не из текущего окна, а из родительского!
    Вроде не супер сложно, но про это мало где напишут, и такой прикол только в -ИЕ8 есть.
    • –4
      Ну так в топку говеные браузеры!
      • +2
        Ну так да! )
      • 0
        Не верю, что можно любить IE. Наверняка все дело в корпоративном критинизме и обязаловке
        • 0
          Вот из-за копроративного кретинизма он всё никак и не подохнет.

          И не понимаю, почему бы не указывать в системных требованиях к ПО наличие нормального браузера?
  • +2
    В саппорт бы этот баг отправитью
    • +6
      вы говорите так, будто его будут исправлять
      • +1
        Не знаю. По крайней мере у меня была бы спокойна совесть, если я отправлю багрепорт. Не исправят — это уже на их совести.
        • 0
          А вы уверены, что этот баг присутствует в. 9ке, или 10ке? :)
          • 0
            Не уверен. Но за отправку багрепорта никого не расстреляют. К тому же, в ИЕ9 есть режим эмуляции как ИЕ7, так и ИЕ8. Вдруг этот баг и в режимах эмуляции проявляется? Эмулировать, так в деталях)))
        • 0
          К сожалению, репорт не избавляет от необходимости обходить этот баг. По моему опыту, у разных пользователей могут быть разные, порой даже очень старые версии IE. Был случай, когда приходилось проверять скрипты в трёх(!) разных билдах одной и той же версии IE, т.к. в каждой версии были свои уникальные тараканы.
          • +1
            Не спорю. Но багрепорт повысит шансы на сокращение версий ИЕ8 с таким багом. Если фикс выпустят, конечно же.
            • 0
              багрепорт однозначно отправлять надо, там ведь умные ребята и исправят со следующей версией!
              • 0
                Ну, как бы следующая версия уже вышла))) ИЕ9)
  • +4
    Картинка — супер! сорри за оффтопик
  • +1
    а кто-нибудь сталкивался с утечками памяти в ie8 при разработке на gwt и gxt? Браузер менять не имеется возможности. только ie. Как их отлавливать в огромном проекте?
  • 0
    Буквально совсем недавно очень долго искали, куда утекает 1 Гб (!) памяти в IE8 на одной единственной странице при добавлении в DOM-дерево таблицы с данными. В таблице во многих ячейках присутствовал inpyt[type=«file»] (скрытый), а он, как оказалось, в IE8 под WinXP почему-то съедает изрядный кусок памяти на каждый экземпляр. Убрали инпуты — потребление памяти уменьшилось до сотни мегабайт. Самое забавное: под Win7 тот же IE8 на той же странице проблем с памятью не было.

    Вывод: если поддерживаете IE8 и ниже, то избегайте большого числа inpyt[type=«file»] на одной странице. Иначе ему сильно плохеет :(
  • –1
    Добавьте в теги «мышки, кактус, кушать».

    Сколько можно поддерживать недобраузеры? Неужели проблема заявить в системных требованиях наличие нормального браузера?
    Пока осла будут поддерживать разработчики — его будут использовать.
    • 0
      Блин, ну парни-то работают и наверняка предлагают самое лучшее решение, беда в том, что руководство недалеко смотрит
  • 0
    Было уже тут слово «решето»? :)
  • 0
    Вау!

    Переключил IE для Хабра в «режим совместимости» — просто полетел по сравнению с обычным режимом!

    Спасибо за идею :)

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