2009-09-21 1 views
6

У меня есть веб-страница, которая пропускает память как в IE8, так и в Firefox; использование памяти, отображаемое в Проводнике Windows, постоянно растет с течением времени.Утечка памяти с помощью jQuery Ajax-запросов

Следующая страница запрашивает url «unplanned.json», который является статическим файлом, который никогда не меняется (хотя я устанавливаю свой HTTP-заголовок Cache-control на no-cache, чтобы убедиться, что запрос Ajax всегда проходит). Когда он получает результаты, он очищает таблицу HTML, перебирает массив json, полученный с сервера, и динамически добавляет строку в таблицу HTML для каждой записи в массиве. Затем он ждет 2 секунды и повторяет этот процесс.

Вот весь веб-страницы:

<html> <head> 
    <title>Test Page</title> 
    <script type="text/javascript" 
    src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> 
</head> <body> 
<script type="text/javascript"> 
    function kickoff() { 
     $.getJSON("unplanned.json", resetTable); 
    } 
    function resetTable(rows) { 
     $("#content tbody").empty(); 
     for(var i=0; i<rows.length; i++) { 
      $("<tr>" 
       + "<td>" + rows[i].mpe_name + "</td>" 
       + "<td>" + rows[i].bin + "</td>" 
       + "<td>" + rows[i].request_time + "</td>" 
       + "<td>" + rows[i].filtered_delta + "</td>" 
       + "<td>" + rows[i].failed_delta + "</td>" 
      + "</tr>").appendTo("#content tbody"); 
     } 
     setTimeout(kickoff, 2000); 
    } 
    $(kickoff); 
</script> 
<table id="content" border="1" style="width:100% ; text-align:center"> 
<thead><tr> 
    <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th> 
</tr></thead> 
<tbody></tbody> 
</table> 
</body> </html> 

Если это помогает, вот пример JSON я отправляю обратно (именно это точный массив wuith тысяч записей вместо одного):

[ 
    { 
     mpe_name: "DBOSS-995", 
     request_time: "09/18/2009 11:51:06", 
     bin: 4, 
     filtered_delta: 1, 
     failed_delta: 1 
    } 
] 

EDIT: Я принял чрезвычайно полезный ответ Торан, но я чувствую, что я должен разместить дополнительный код, так как его removefromdom JQuery плагин имеет некоторые ограничения:

  • Он удаляет только отдельные элементы. Поэтому вы не можете дать ему запрос типа `$ (" # content tbody tr ")` и ожидать, что он удалит все указанные вами элементы.
  • Любой элемент, который вы удаляете с ним, должен иметь атрибут id. Поэтому, если я хочу удалить свой «tbody», тогда я должен назначить `id` моему тегу tbody, иначе он даст ошибку.
  • Он удаляет сам элемент и все его потомки, поэтому, если вы просто хотите опорожнить этот элемент, вам придется его повторно создать (или изменить плагин на пустой, а не удалить).

Так вот моя страница выше, чтобы использовать плагин Toran. Для простоты я не применял ни один из общих рекомендаций по производительности offered by Peter. Здесь нет страницы, которые сейчас не больше утечек памяти:

<html> 
<head> 
    <title>Test Page</title> 
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> 
</head> 
<body> 
<script type="text/javascript"> 
<!-- 
    $.fn.removefromdom = function(s) { 
     if (!this) return; 

     var el = document.getElementById(this.attr("id")); 

     if (!el) return; 

     var bin = document.getElementById("IELeakGarbageBin"); 

     //before deleting el, recursively delete all of its children. 
     while (el.childNodes.length > 0) { 
      if (!bin) { 
       bin = document.createElement("DIV"); 
       bin.id = "IELeakGarbageBin"; 
       document.body.appendChild(bin); 
      } 

      bin.appendChild(el.childNodes[el.childNodes.length - 1]); 
      bin.innerHTML = ""; 
     } 

     el.parentNode.removeChild(el); 

     if (!bin) { 
      bin = document.createElement("DIV"); 
      bin.id = "IELeakGarbageBin"; 
      document.body.appendChild(bin); 
     } 

     bin.appendChild(el); 
     bin.innerHTML = ""; 
    }; 

    var resets = 0; 
    function kickoff() { 
     $.getJSON("unplanned.json", resetTable); 
    } 
    function resetTable(rows) { 
     $("#content tbody").removefromdom(); 
     $("#content").append('<tbody id="id_field_required"></tbody>'); 
     for(var i=0; i<rows.length; i++) { 
      $("#content tbody").append("<tr><td>" + rows[i].mpe_name + "</td>" 
       + "<td>" + rows[i].bin + "</td>" 
       + "<td>" + rows[i].request_time + "</td>" 
       + "<td>" + rows[i].filtered_delta + "</td>" 
       + "<td>" + rows[i].failed_delta + "</td></tr>"); 
     } 
     resets++; 
     $("#message").html("Content set this many times: " + resets); 
     setTimeout(kickoff, 2000); 
    } 
    $(kickoff); 
// --> 
</script> 
<div id="message" style="color:red"></div> 
<table id="content" border="1" style="width:100% ; text-align:center"> 
<thead><tr> 
    <th>MPE</th> 
    <th>Bin</th> 
    <th>When</th> 
    <th>Filtered</th> 
    <th>Failed</th> 
</tr></thead> 
<tbody id="id_field_required"></tbody> 
</table> 
</body> 
</html> 

ДАЛЕЕ EDIT: Я оставлю мой вопрос без изменений, хотя стоит отметить, что эта утечка памяти не имеет ничего общего с Ajax. На самом деле, следующий код будет утечка памяти так же и быть столь же легко решается с removefromdom JQuery плагин Торан в:

function resetTable() { 
    $("#content tbody").empty(); 
    for(var i=0; i<1000; i++) { 
     $("#content tbody").append("<tr><td>" + "DBOSS-095" + "</td>" 
      + "<td>" + 4 + "</td>" 
      + "<td>" + "09/18/2009 11:51:06" + "</td>" 
      + "<td>" + 1 + "</td>" 
      + "<td>" + 1 + "</td></tr>"); 
    } 
    setTimeout(resetTable, 2000); 
} 
$(resetTable); 
+0

Невнятный вопрос, но поскольку он добавляет тело к каждой выборке, как он может использовать один и тот же объем памяти? –

+0

Он освобождает тём, прежде чем присоединяться к нему. Я ожидал бы, что опустошенные ряды собирают мусор, хотя кто-то должен определенно сказать мне, ошибаюсь ли я в этом. –

+1

В IE6/7/8 вы не можете по-настоящему удалить динамически созданный элемент DOM, не делая странного рабочего процесса, чтобы обнулить внутреннийHTML (set innerHTML = "") –

ответ

11

Я не уверен, почему firefox не доволен этим, но я могу сказать по опыту, что в IE6/7/8 вы должны установить innerHTML = ""; на объект, который вы хотите удалить из DOM. (если вы создали этот элемент DOM динамически, то есть)

$("#content tbody").empty(); может не выпускать эти динамически сгенерированные элементы DOM.

Вместо этого попробуйте что-то вроде ниже (это плагин jQuery, который я написал для решения проблемы).

jQuery.fn.removefromdom = function(s) { 
    if (!this) return; 

    var bin = $("#IELeakGarbageBin"); 

    if (!bin.get(0)) { 
     bin = $("<div id='IELeakGarbageBin'></div>"); 
     $("body").append(bin); 
    } 

    $(this).children().each(
      function() { 
       bin.append(this); 
       document.getElementById("IELeakGarbageBin").innerHTML = ""; 
      } 
    ); 

    this.remove(); 

    bin.append(this); 
    document.getElementById("IELeakGarbageBin").innerHTML = ""; 
}; 

Вы назвали бы это так: $("#content").removefromdom();

Единственная проблема здесь в том, что вам необходимо повторно создать свой стол каждый раз, когда вы хотите, чтобы построить его.

Кроме того, если это решит вашу проблему в IE, вы можете прочитать об этом в блоге post, который я написал в начале этого года, когда столкнулся с той же проблемой.

Редактировать Я обновил плагин выше, чтобы быть на 95% свободным от JavaScript, поэтому он использует больше jQuery, чем предыдущая версия. Вы все равно заметите, что мне нужно использовать innerHTML, потому что функция jQuery html (""); не действует одинаково для IE6/7/8

+0

Большое спасибо, это исправило мою проблему. Я отредактировал свой вопрос, чтобы указать некоторые ограничения этого плагина и опубликовал мой код, который теперь не работает, и не показывать память, чтобы показать людям, как применять ваш плагин к моей странице. –

4

Я не уверен по поводу утечки, но ваша resetTable() функция очень неэффективно. Попробуйте сначала исправить эти проблемы и посмотреть, где вы закончите.

  • Не добавляйте к DOM в цикле. Если вы должны сделать DOM-манипуляцию, затем добавить к фрагменту документа, а затем перенести этот фрагмент в DOM.
  • Но innerHTML быстрее, чем манипуляция DOM, так что используйте это, если сможете.
  • Хранить jQuery в локальных переменных - не нужно повторно запускать селектор каждый раз.
  • Также сохраняйте повторяющиеся ссылки в локальной переменной.
  • При повторении по любой коллекции сохраните длину в локальной переменной.

Новый код:

<html> <head> 
    <title>Test Page</title> 
    <script type="text/javascript" 
    src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> 
</head> <body> 
<script type="text/javascript"> 
$(function() 
{ 
    var $tbody = $("#content tbody"); 

    function kickoff() { 
     $.getJSON("test.php", resetTable); 
    } 

    function resetTable(rows) 
    { 
     var html = '' 
      , i = 0 
      , l = rows.length 
      , row; 
     for (; i < l; i++) 
     { 
      row = rows[i]; 
      html += "<tr>" 
       + "<td>" + row.mpe_name + "</td>" 
       + "<td>" + row.bin + "</td>" 
       + "<td>" + row.request_time + "</td>" 
       + "<td>" + row.filtered_delta + "</td>" 
       + "<td>" + row.failed_delta + "</td>" 
      + "</tr>"; 
     } 
     $tbody.html(html); 
     setTimeout(kickoff, 2000); 
    } 

    kickoff(); 
}); 
</script> 
<table id="content" border="1" style="width:100% ; text-align:center"> 
<thead> 
    <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th> 
</thead> 
<tbody></tbody> 
</table> 
</body> </html> 

Ссылки:

+0

Вот некоторые замечательные предложения, но, к сожалению, я все еще вижу память при запуске вашего кода. Все точно так же, за исключением того, что я заменил «test.php» на «unplanned.json» - я предполагаю, что вы сделали страницу test.php, чтобы проверить это сами, что действительно потрясающе и полезно. Поэтому спасибо за ввод, и я обязательно буду использовать некоторые из этих методов в будущем, но если у вас есть другие идеи о том, что может вызвать использование этой памяти, я бы хотел их услышать. –

+1

сделал. Я еще подумаю об этой проблеме и посмотрю, смогу ли я что-нибудь придумать. Каков размер данных, которые вы возвращаете в AJAX? –

0

Пожалуйста, поправьте меня, если я ошибаюсь здесь, но не SetTimeout (fn) предотвращает выпуск ca ller? Итак, все переменные/память, выделенные во время метода resetTable (rows), останутся выделенными до тех пор, пока цикл не завершится?

Если это так, то нажатие строковой конструкции и добавление логики к другому методу может быть немного лучше, поскольку эти объекты будут освобождены после каждого вызова и только память возвращаемого значения (в этом случае либо разметка строки или ничего, если новый метод сделал appendTo()), останется в памяти.

В сущности:

первоначальный вызов зарезки

-> Вызовы Сбрасываемая()

-> -> SetTimeout вызовы зарезки снова

-> -> -> Вызовы Сбрасываемая () снова

-> -> -> -> Продолжить до Бесконечные

Если код никогда не будет разрешен, дерево будет продолжать расти.

Альтернативный способ освободить часть памяти, основанной на это будет, как код ниже:

function resetTable(rows) { 
    appendRows(rows); 
    setTimeout(kickoff, 2000); 
} 
function appendRows(rows) 
{ 
    var rowMarkup = ''; 
    var length = rows.length 
    var row; 

    for (i = 0; i < length; i++) 
    { 
     row = rows[i]; 
     rowMarkup += "<tr>" 
       + "<td>" + row.mpe_name + "</td>" 
       + "<td>" + row.bin + "</td>" 
       + "<td>" + row.request_time + "</td>" 
       + "<td>" + row.filtered_delta + "</td>" 
       + "<td>" + row.failed_delta + "</td>" 
       + "</tr>";  
    } 

    $("#content tbody").html(rowMarkup); 
} 

Это добавит разметку для TBODY, а затем закончить эту часть стека. Я уверен, что каждая итерация «строк» ​​останется в памяти; однако строка разметки и таковая должны в конечном итоге высвободиться.

Снова ... прошло некоторое время, так как я смотрел SetTimeout на этом низком уровне, поэтому я мог быть совершенно неправ здесь. В любом случае это не устранит утечку и только возможно уменьшит темпы роста. Это зависит от того, как сборщик мусора используемых JavaScript-движков имеет дело с петлями SetTimeout, как вы здесь.

+0

Спасибо за предложение - я получил свою страницу, благодаря предложению Торана выше, но когда-нибудь в будущем я проверю ваши идеи и посмотрю, будут ли они работать. Если они это сделают, я, скорее всего, соглашусь с вашим предложением, так как это намного проще, чем плагин jQuery, который написал Торан, хотя я предполагаю, что его подход, вероятно, единственный реальный способ заставить мою страницу не утечки памяти. –