2009-09-22 28 views
45

Aquí hay una página muerta que pierde memoria en IE8 usando jQuery (detecto pérdidas de memoria al ver el uso de memoria de mi proceso iexplore.exe crecer con el tiempo en el Administrador de tareas de Windows):jQuery pérdida de memoria con DOM eliminación

<html> 
<head> 
    <title>Test Page</title> 
    <script type="text/javascript" src="jquery.js"></script> 
</head> 
<body> 
<script type="text/javascript"> 
    function resetContent() { 
     $("#content div").remove(); 
     for(var i=0; i<10000; i++) { 
      $("#content").append("<div>Hello World!</div>"); 
     } 
     setTimeout(resetTable, 2000); 
    } 
    $(resetContent); 
</script> 
<div id="content"></div> 
</body> 
</html> 

Al parecer, incluso cuando llamo a la función jQuery.remove(), sigo experimentando algunas pérdidas de memoria. Puedo escribir mi propia función Remove que no experimenta ninguna pérdida de memoria de la siguiente manera:

$.fn.removeWithoutLeaking = function() { 
    this.each(function(i,e){ 
     if(e.parentNode) 
      e.parentNode.removeChild(e); 
    }); 
}; 

Esto funciona muy bien y no se escape ninguna memoria. Entonces, ¿por qué jQuery pierde memoria? He creado otra función Remove basado en jQuery.remove() y esto en verdad causa una fuga:

$.fn.removeWithLeakage = function() { 
    this.each(function(i,e) { 
     $("*", e).add([e]).each(function(){ 
      $.event.remove(this); 
      $.removeData(this); 
     }); 
     if (e.parentNode) 
      e.parentNode.removeChild(e); 
    }); 
}; 

Curiosamente, la pérdida de memoria parece ser causada por la cada llamada que jQuery incluye para evitar pérdidas de memoria de eventos y datos asociados con el Elementos DOM borrados. Cuando llamo a la función removeWithoutLeaking, mi memoria se mantiene constante a lo largo del tiempo, pero cuando llamo a removeWithLeakage, esta sigue creciendo.

Mi pregunta es, ¿qué hay de que cada llamada

$("*", e).add([e]).each(function(){ 
    $.event.remove(this); 
    $.removeData(this); 
}); 

podría ser la causa de la pérdida de memoria?

EDIT: typo fijo en código que, tras la repetición de pruebas, ha demostrado no tener ningún efecto sobre los resultados.

hacer otras modificaciones: He presentado un informe de error con el proyecto jQuery, ya que esto parece ser un error jQuery: http://dev.jquery.com/ticket/5285

+1

Es una buena pregunta y felicitaciones por encontrar esto si realmente es un error. Sin embargo, es posible que obtengas una mejor respuesta de los chicos de jQuery, te sugiero que comiences una entrada con ellos si estás seguro. http://dev.jquery.com/ – womp

+1

Interesante. No puedo ver inmediatamente ninguno de los sospechosos habituales de fuga de memoria de IE donde se obtiene un bucle de referencia entre un objeto de host y un objeto nativo, ¿quizás se trata de un nuevo tipo de fuga? ¿Sabes si ocurre solo en IE8 Standards Mode, o en IE7-compatibility mode, o Quirks, o los tres? – bobince

+0

@Eli - ¿debo publicar la fuente para cada() al final de la pregunta? –

Respuesta

57

pensé que David podría ser en algo con la presunta filtración removeChild, pero no podrá reproducirla de IE8 ... que bien puede suceder en los navegadores anteriores, pero eso no es lo que tenemos aquí. Si elimino manualmente los DDs no hay fugas; si alterno jQuery para usar outerHTML= '' (o mover a bin seguido de bin.innerHTML) en lugar de removeChild, todavía hay una fuga.

En un proceso de eliminación empecé cortando trozos de remove en jQuery. línea 1244 en 1.3.2:

//jQuery.event.remove(this); 
jQuery.removeData(this); 

Comentando esa línea no se produjo ninguna fuga.

Por lo tanto, echemos un vistazo a event.remove, llama al data('events') para ver si hay algún evento asociado al elemento. ¿Qué está haciendo data?

// Compute a unique ID for the element 
if (!id) 
    id = elem[ expando ] = ++uuid; 

Oh.Por lo tanto, agrega una de las propiedades de hackeo de entrada uuid-to-data-jupiter de jQuery para cada elemento al que intente leer datos, que incluye cada descendiente de un elemento que está eliminando. Que tonto. Me puede causar un cortocircuito que al poner esta línea justo antes de que:

// Don't create ID/lookup if we're only reading non-present data 
if (!id && data===undefined) 
    return undefined; 

que aparece para reparar la fuga para este caso en IE8. No puedo garantizar que no rompa algo más en el laberinto que es jQuery, pero lógicamente tiene sentido.

Por lo que puedo averiguar, la fuga es simplemente el objeto jQuery.cache (que es el almacén de datos, no es realmente un caché como tal) cada vez más grande cuando se agrega una nueva clave para cada elemento eliminado. Aunque removeData debería eliminar esas entradas de caché, OK, IE no parece recuperar el espacio cuando delete una clave de un objeto.

(De cualquier manera, este es un ejemplo del tipo de comportamiento de jQuery que no aprecio. Está haciendo demasiado por debajo de lo que debería ser una operación trivialmente simple ... algo de lo cual es bastante cuestionable cosas. todo esto con el expando y what jQuery does to innerHTML via regex to prevent that showing as an attribute in IE se acaba de romper y feo. y el hábito de hacer el getter y setter la misma función es confuso y, aquí, resultados en el error.)

[Extrañamente, dejando el LeakTest durante largos períodos de tiempo terminaron ocasionalmente dando errores totalmente espurios en jquery.js antes de que la memoria se acabara realmente ... había algo así como 'comando inesperado', y noté que 'nodeName es nulo o no es un objeto' en la línea 667 , que por lo que puedo ver ni siquiera debería haber sido ru ¡n, y mucho menos que haya un cheque allí para que nodeName sea nulo! IE no me está dando mucha confianza aquí ...]

+9

Impresionante trabajo. – recursive

+0

Sí, eso definitivamente lo soluciona. Probablemente terminaré publicando otra pregunta preguntándome por qué, ya que como señala la función jQuery.removeData debería deshacerse de estas entradas. Oh, bueno, muchas gracias por la gran ayuda. –

+1

Elimina las entradas, pero el objeto de caché, incluso con menos propiedades, no reduce el uso de memoria. Tal vez intente volver a usar los identificadores manteniendo un contador de "identificador gratuito más bajo" en lugar de generar un nuevo uuid cada vez. De cualquier manera, leer datos de un objeto sin datos no debe agregar un contenedor de datos vacío. – bobince

1

¿Todavía fugas si se llama empty en lugar de remove?

$("#content").empty(); 
+1

Sí, lo intenté. Incluso miré bajo el capó en jQuery y encontré que las llamadas vacías llaman quitar para hacer la mayor parte de su trabajo. –

2

Véase la hoja de ruta de jQuery 1.4 en http://docs.jquery.com/JQuery_1.4_Roadmap. Específicamente, la sección "Usar .outerHTML para la limpieza después de .remove()" trata problemas de pérdida de memoria que ocurren en IE debido a la función de eliminación que se llama.

Quizás sus problemas se resolverán con la próxima versión.

4

La eliminación de elementos es un problema DOM inherente. Que se quedará con nosotros Ídem.

jQuery.fn.flush = function() 
/// <summary> 
/// $().flush() re-makes the current element stack inside $() 
/// thus flushing-out the non-referenced elements 
/// left inside after numerous remove's, append's etc ... 
/// </summary> 
{ return jQuery(this.context).find(this.selector); } 

En lugar de hackear jQ, utilizo esta extensión. Especialmente en las páginas con gran cantidad de quita() y clones():

$exact = $("whatever").append("complex html").remove().flush().clone(); 

Y también la siguiente ¿Ayuda:

// remove all event bindings , 
// and the jQ data made for jQ event handling 
jQuery.unbindall = function() { jQuery('*').unbind(); } 
// 
$(document).unload(function() { 
    jQuery.unbindall(); 
}); 
5

parece estar fijo en jQuery 1.5 (versión 23. Febrero) . Me encontré con el mismo problema con 1.4.2 y lo arreglé primero con eliminación de dom como se indica arriba y luego probando la nueva versión.

1

jQuery 1.4.1 ha siguientes:

cleanData: function (elems) { 
     for (var i = 0, elem, id; (elem = elems[i]) != null; i++) { 
      jQuery.event.remove(elem); 
      jQuery.removeData(elem); 
     } 
    } 

Esto es lo que tuve que modificar para eliminar fugas problema:

cleanData: function (elems) { 
     for (var i = 0, elem, id; (elem = elems[i]) != null; i++) { 
      jQuery.event.remove(elem); 
      jQuery.removeData(elem); 
      jQuery.purge(elem); 
     } 
    } 

añadido función:

purge: function (d) { 
     var a = d.childNodes; 
     if (a) { 
      var remove = false; 
      while (!remove) { 
       var l = a.length; 
       for (i = 0; i < l; i += 1) { 
        var child = a[i]; 
        if (child.childNodes.length == 0) { 
         jQuery.event.remove(child); 
         d.removeChild(child); 
         remove = true; 
         break; 
        } 
        else { 
         jQuery.purge(child); 
        } 
       } 
       if (remove) { 
        remove = false; 
       } else { 
        break; 
       } 
      } 
     } 
    }, 
+0

Cambié la purga para hacerlo aún más eficiente (al menos 30%) – Basil

+1

No solo esto NO funciona, en grandes conjuntos de elementos hace IE chug. Se mantuvo durante 10 minutos mientras aumentaba el uso de la memoria, el conteo de hilos y el uso de la CPU. Funciona bien para una cantidad pequeña de elementos, pero no para grandes cantidades que usan mucha memoria ... que es el problema en primer lugar. – AS7K