2009-09-21 15 views
6

Tengo una página web que pierde memoria tanto en IE8 como en Firefox; el uso de la memoria que se muestra en Windows Process Explorer simplemente sigue creciendo con el tiempo.Perdida de memoria que implica solicitudes de jQuery Ajax

La siguiente página solicita la url "unplanned.json", que es un archivo estático que nunca cambia (aunque configuro mi encabezado HTTP Cache-control en no-cache para asegurarme de que la solicitud Ajax siempre se realiza). Cuando obtiene los resultados, borra una tabla HTML, realiza un bucle sobre la matriz json que recuperó del servidor y agrega dinámicamente una fila a una tabla HTML para cada entrada en la matriz. Luego espera 2 segundos y repite este proceso.

Aquí está toda la página web:

<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> 

Si ayuda, he aquí un ejemplo de la JSON que estoy enviando de vuelta (que es esta matriz exacta no se enfrentó a miles de entradas en lugar de sólo uno):

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

EDIT: he aceptado respuesta extremadamente útiles de Toran, pero siento que debo escribir algo de código adicional, ya que su removefromdom jQuery plugin tiene algunas limitaciones:

  • Solo elimina elementos individuales. Por lo tanto, no puede darle una consulta como `$ (" # content tbody tr ")` y esperar que elimine todos los elementos que ha especificado.
  • Cualquier elemento que elimine debe tener un atributo `id`. Entonces, si quiero eliminar mi `tbody`, entonces debo asignar un` id` a mi etiqueta `tbody` o de lo contrario dará un error.
  • Elimina el elemento en sí y todos sus descendientes, por lo que si simplemente desea vaciar ese elemento, deberá volver a crearlo posteriormente (o modificar el complemento para vaciarlo en lugar de eliminarlo).

Así que aquí está mi página modificada para usar el plugin de Toran. En aras de la simplicidad, no apliqué ninguna de las recomendaciones de rendimiento general offered by Peter. Aquí está la página que ahora no hay pérdidas de memoria más largos:

<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> 

hacer otras modificaciones: Voy a dejar mi pregunta sin cambios, aunque vale la pena señalar que esta pérdida de memoria no tiene nada que ver con el Ajax. De hecho, el siguiente código pérdida de memoria de la misma manera y se acaba de resolverse con la misma facilidad con removefromdom plugin de jQuery de Toran:

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

pregunta tonta, pero dado que se agrega al cuerpo en cada búsqueda, ¿cómo puede utilizar la misma cantidad de memoria? –

+0

Vacía el tbody antes de anexarlo. Esperaría que las filas vacías fueran recogidas, aunque alguien definitivamente debería decirme si me equivoco al respecto. –

+1

En IE6/7/8 no se puede eliminar realmente un elemento DOM creado dinámicamente sin hacer una extraña solución para anular el innerHTML (set innerHTML = "") –

Respuesta

11

No estoy seguro de por qué firefox no está contento con esto, pero puedo decir por experiencia que en IE6/7/8 debe establecer innerHTML = ""; en el objeto que desea eliminar del DOM. (si ha creado dinámicamente este elemento DOM)

$("#content tbody").empty(); podría no estar liberando estos elementos DOM generados dinámicamente.

En su lugar, intente algo como el siguiente (este es un plugin jQuery que escribí para resolver el problema).

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 = ""; 
}; 

Se podría llamar a este modo: $("#content").removefromdom();

El único problema aquí es que usted necesita para volver a crear la tabla cada vez que desee para su construcción.

Además, si esto resuelve su problema en IE, puede leer más sobre esto en un blog post que escribí a principios de este año cuando me encontré con el mismo problema.

Edité Actualicé el complemento anterior para que sea 95% JavaScript gratis ahora, así que está usando más jQuery que la versión anterior. Todavía notarás que tengo que usar innerHTML porque la función jQuery html (""); no funciona igual para IE6/7/8

+0

Muchas gracias, esto solucionó mi problema. He editado mi pregunta para señalar algunas limitaciones de este complemento y publiqué mi código que ahora funciona y no tiene pérdida de memoria para mostrar a las personas cómo aplicar el complemento a mi página. –

4

No estoy seguro acerca de la fuga, pero su función resetTable() es muy ineficiente. Intente solucionar esos problemas primero y vea dónde termina.

  • No anexar al DOM en un bucle. Si debe hacer la manipulación de DOM, a continuación, anexar a un fragmento de documento, y luego mover ese fragmento al DOM.
  • Pero innerHTML es más rápido que la manipulación DOM de todos modos, así que úsala si puedes.
  • Tienda jQuery se establece en variables locales; no es necesario volver a ejecutar el selector cada vez.
  • También almacenar referencias repetidas en una variable local.
  • Al iterar sobre una colección de cualquier tipo, también almacene la longitud en una variable local.

Nuevo código:

<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> 

Referencias:

+0

Esas son algunas sugerencias geniales, pero desafortunadamente todavía veo la memoria fugas al ejecutar su código. Todo es exactamente igual excepto que reemplacé "test.php" con "unplanned.json" - Supongo que creó una página test.php para probarlo usted mismo, lo cual es realmente asombroso y útil. Así que gracias por la contribución y definitivamente usaré algunas de estas técnicas en el futuro, pero si tienes alguna otra idea sobre lo que podría estar causando este uso de memoria, entonces me encantaría escucharlas. –

+1

Lo hice. Pensaré en este problema un poco más y veré si puedo encontrar algo. ¿Cuál es el tamaño de los datos que está devolviendo a AJAX? –

0

Por favor, corríjanme si me equivoco aquí, pero no setTimeout (fn) impide la liberación de la ca ¿espacio de memoria de ller? ¿Para que todas las variables/memoria asignadas durante el método resetTable (filas) permanezcan asignadas hasta que termine el ciclo?

Si ese es el caso, empujar la construcción de cadenas y appendTo logic a un método diferente puede ser un poco mejor ya que esos objetos se liberarían después de cada llamada y solo la memoria del valor devuelto (en este caso, el marcado de cadenas) o nada si el nuevo método dejaba el appendTo()) en la memoria.

En esencia:

llamada inicial hasta su inicio

-> Llamadas reseteable()

-> -> SetTimeOut las primeras reuniones de nuevo

-> -> -> Llamadas reseteable () otra vez

-> -> -> -> Continuar hasta Infinite

Si el código nunca se resuelve de verdad, el árbol continuará creciendo.

Una forma alternativa de liberar algo de memoria basado en esto sería como el código de abajo:

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); 
} 

Esto añadirá el margen de beneficio a su tbody y luego terminar esa parte de la pila. Estoy bastante seguro de que cada iteración de "filas" permanecerá en la memoria; sin embargo, la cadena de marcado y tal debería liberarse eventualmente.

Nuevamente ... hace mucho que no veo SetTimeout en este nivel bajo, así que podría estar completamente equivocado aquí. En cualquier caso, esto no eliminará la fuga y solo posiblemente disminuirá la tasa de crecimiento. Depende de cómo el recolector de basura de los motores de JavaScript en uso se ocupa de los bucles SetTimeout como los que tiene aquí.

+0

Gracias por la sugerencia: hice que mi página funcionara gracias a la sugerencia de Toran anterior, pero en algún momento en el futuro revisaré sus ideas y veré si también funcionan. Si lo hacen, probablemente acepte su sugerencia en su lugar, ya que es mucho más simple que el plugin jQuery que Toran escribió, aunque supongo que su enfoque es probablemente la única manera real de lograr que mi página no pierda memoria. –

Cuestiones relacionadas