Encontrar fugas de memoria JavaScript con Chrome


He creado un caso de prueba muy simple que crea una vista troncal, adjunta un controlador a un evento e instanciauna clase definida por el usuario. Creo que al hacer clic en el botón" Eliminar " en esta muestra, todo se limpiará y no debería haber fugas de memoria.

Un jsfiddle para el código está aquí: http://jsfiddle.net/4QhR2/

// scope everything to a function
function main() {

    function MyWrapper() {
        this.element = null;
    }
    MyWrapper.prototype.set = function(elem) {
        this.element = elem;
    }
    MyWrapper.prototype.get = function() {
        return this.element;
    }

    var MyView = Backbone.View.extend({
        tagName : "div",
        id : "view",
        events : {
            "click #button" : "onButton",
        },    
        initialize : function(options) {        
            // done for demo purposes only, should be using templates
            this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";        
            this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
        },
        render : function() {        
            this.$el.html(this.html_text);

            this.wrapper = new MyWrapper();
            this.wrapper.set(this.$("#textbox"));
            this.wrapper.get().val("placeholder");

            return this;
        },
        onButton : function() {
            // assume this gets .remove() called on subviews (if they existed)
            this.trigger("cleanup");
            this.remove();
        }
    });

    var view = new MyView();
    $("#content").append(view.render().el);
}

main();

Sin embargo, no estoy claro cómo utilizar el perfilador de Google Chrome para verificar que este es, de hecho, el caso. Hay un gazillion cosas que aparecen en la instantánea del generador de perfiles de montón, y no tengo idea de cómo decodificar lo que es bueno/malo. Los tutoriales que he visto de él hasta ahora sólo me dice "usar la instantánea profiler" o me dan un muy detallado de manifiesto en todo el analizador de obras. ¿Es posible simplemente usar el perfilador como una herramienta, o realmente tengo que entender cómo se diseñó todo?

EDITAR: Tutoriales como estos:

Fuga de memoria de Gmail fijación

Usando DevTools

Son representativos de algunos de los materiales más fuertes que hay, por lo que he visto. Sin embargo, más allá de introducir el concepto de la Técnica 3 Snapshot, encuentro que ofrecen muy poco en términos de conocimiento práctico (para un principiante como yo). El tutorial 'Usando DevTools' no funciona a través de un ejemplo real, por lo que su descripción conceptual general y vaga de las cosas no es demasiado útil. En cuanto al 'Gmail' ejemplo:

Así que encontraste una fuga. ¿Y ahora qué?

  • Examinar la ruta de retención de objetos filtrados en la mitad inferior del panel de perfiles

  • Si el sitio de asignación no se puede inferir fácilmente (es decir, oyentes de eventos):

  • Instrumente el constructor del objeto de retención a través de la consola JS para guardar el seguimiento de la pila para asignaciones

  • ¿Usando Un Cierre? Habilitar la adecuada existente bandera (es decir, goog.evento.Oyente.ENABLE_MONITORING) para establecer la propiedad creationStack durante la construcción

Me encuentro más confundido después de leer eso, no menos. Y, de nuevo, me está diciendo que haga cosas, no cómo hacerlas. Desde mi perspectiva, toda la información que hay es demasiado vaga o solo tendría sentido para alguien que ya entendió el proceso.

Algunas de estas cuestiones más específicas se han planteado en La respuesta de @ Jonathan Naguin abajo.

Author: Community, 2013-10-27

7 answers

Un buen flujo de trabajo para encontrar fugas de memoria es la técnica three snapshot, utilizada por primera vez por Loreena Lee y el equipo de Gmail para resolver algunos de sus problemas de memoria. Los pasos son, en general:

  • Tome una instantánea de montón.
  • Hacer cosas.
  • Tome otra instantánea de montón.
  • Repite lo mismo.
  • Tome otra instantánea de montón.
  • Filtre los objetos asignados entre las instantáneas 1 y 2 en la vista "Resumen" de la instantánea 3.

Para su ejemplo, yo ha adaptado el código para mostrar este proceso (lo puede encontrar aquí) retrasando la creación de la vista Backbone hasta el evento click del botón Start. Ahora:

  • Ejecute el HTML (guardado localmente de usar esta dirección ) y tome una instantánea.
  • Haga clic en Inicio para crear la vista.
  • Tome otra instantánea.
  • Haga clic en eliminar.
  • Tome otra instantánea.
  • Filtrar objetos asignados entre las instantáneas 1 y 2 en el resumen de la instantánea 3" vista.

Ahora usted está listo para encontrar fugas de memoria!

Notará nodos de unos pocos colores diferentes. Los nodos rojos no tienen referencias directas de Javascript a ellos, pero están vivos porque son parte de un árbol DOM separado. Puede haber un nodo en el árbol al que se hace referencia desde Javascript (tal vez como un cierre o una variable), pero está impidiendo casualmente que todo el árbol DOM sea recolectado como basura.

introduzca la descripción de la imagen aquí

Nodos amarillos sin embargo tienen directa referencias de Javascript. Busque nodos amarillos en el mismo árbol DOM separado para localizar referencias de su Javascript. Debe haber una cadena de propiedades que conduzca desde la ventana DOM hasta el elemento.

En su particular puede ver un elemento HTML Div marcado como rojo. Si expande el elemento verá que es referenciado por una función "cache".

introduzca la descripción de la imagen aquí

Seleccione la fila y en su consola escriba $0, verá la función real y ubicación:

>$0
function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push( key += " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key ] = value);
    }                                                     jquery-2.0.2.js:1166

Aquí es donde se hace referencia a su elemento. Desafortunadamente no hay mucho que puedas hacer, es un mecanismo interno de jQuery. Pero, solo para fines de prueba, vaya a la función y cambie el método a:

function cache( key, value ) {
    return value;
}

Ahora, si repite el proceso, no verá ningún nodo rojo:)

Documentación:

 181
Author: Jonathan Naguin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-01 12:37:21

Aquí hay un consejo sobre el perfil de memoria de un jsfiddle: Use la siguiente URL para aislar su resultado jsfiddle, elimina todo el marco jsfiddle y carga solo su resultado.

Http://jsfiddle.net/4QhR2/show /

Nunca pude averiguar cómo usar la línea de tiempo y el Generador de perfiles para rastrear las fugas de memoria, hasta que leí la siguiente documentación. Después de leer la sección titulada 'Object allocation tracker' pude usar la herramienta' Record Heap Allocations', y rastrear algunos nodos DOM separados.

Solucioné el problema cambiando de enlace de eventos jQuery a usar delegación de eventos Backbone. Tengo entendido que las versiones más recientes de Backbone desvincularán automáticamente los eventos si llamas a View.remove(). Ejecuta algunas de las demostraciones tú mismo, están configuradas con fugas de memoria para que puedas identificarlas. Siéntete libre de hacer preguntas aquí si aún no lo entiendes después de estudiar esto documentación.

Https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling

 6
Author: ricksuggs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-01 13:55:08

Básicamente necesita mirar el número de objetos dentro de su instantánea de montón. Si el número de objetos aumenta entre dos instantáneas y ha desechado objetos, entonces tiene una pérdida de memoria. Mi consejo es buscar controladores de eventos en su código que no se desprendan.

 5
Author: Konstantin Dinev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-27 17:33:30

Es posible que también desee leer:

Http://addyosmani.com/blog/taming-the-unicorn-easing-javascript-memory-profiling-in-devtools/

Explica el uso de las herramientas de desarrollo de Chrome y da algunos consejos paso a paso sobre cómo confirmar y localizar una fuga de memoria utilizando la comparación de instantáneas de montón y las diferentes vistas de instantáneas hep disponibles.

 3
Author: bennidi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-15 09:56:43

Hay un video de introducción de Google, que será muy útil para encontrar fugas de memoria JavaScript.

Https://www.youtube.com/watch?v=L3ugr9BJqIs

 3
Author: 令狐葱,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-14 07:59:59

También puede mirar la pestaña Línea de tiempo en herramientas para desarrolladores. Registre el uso de su aplicación y vigile el nodo DOM y el recuento de oyentes de eventos.

Si el gráfico de memoria indica una fuga de memoria, entonces puede usar el generador de perfiles para averiguar qué se está filtrando.

 2
Author: Robert Falkén,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-30 08:35:52

En segundo lugar el consejo de tomar una instantánea montón, son excelentes para la detección de fugas de memoria, Chrome hace un excelente trabajo de snapshotting.

En mi proyecto de investigación para mi grado estaba construyendo una aplicación web interactiva que tenía que generar una gran cantidad de datos construidos en 'capas', muchas de estas capas serían 'eliminadas' en la interfaz de usuario, pero por alguna razón la memoria no se estaba desasignando, utilizando la herramienta de instantáneas pude determinar que jQuery había estado manteniendo una referencia en objeto (la fuente fue cuando estaba tratando de desencadenar un evento .load() que mantuvo la referencia a pesar de salir del ámbito). Tener esta información a mano por sí solo salvó mi proyecto, es una herramienta muy útil cuando estás usando bibliotecas de otras personas y tienes este problema de referencias persistentes que impiden que el GC haga su trabajo.

EDITAR: También es útil planificar con anticipación qué acciones vas a realizar para minimizar el tiempo dedicado a snapshotting, hipotetizar lo que podría estar causando el problema y probar cada escenario, haciendo instantáneas antes y después.

 2
Author: ProgrammerInProgress,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-06 09:26:41