tutorial style definicion attribute javascript internet-explorer google-chrome firefox garbage-collection

style - ¿Cómo se cierran los cierres de JavaScript?



title css (3)

He registrado el siguiente error de Chrome , que ha provocado muchas pérdidas de memoria graves y no obvias en mi código:

(Estos resultados usan el perfilador de memoria de Chrome Dev Tools, que ejecuta el GC, y luego toma una instantánea del montón de todo lo que no se guardó como basura).

En el siguiente código, la instancia de someClass es basura recolectada (buena):

var someClass = function() {}; function f() { var some = new someClass(); return function() {}; } window.f_ = f();

Pero no será basura recolectada en este caso (mala):

var someClass = function() {}; function f() { var some = new someClass(); function unreachable() { some; } return function() {}; } window.f_ = f();

Y la captura de pantalla correspondiente:

Parece que un cierre (en este caso, function() {} ) mantiene todos los objetos "vivos" si el objeto es referenciado por cualquier otro cierre en el mismo contexto, independientemente de si el cierre en sí mismo es alcanzable o no.

Mi pregunta es sobre la recolección de basura de cierre en otros navegadores (IE 9+ y Firefox). Estoy bastante familiarizado con las herramientas de webkit, como el generador de perfiles de JavaScript, pero sé muy poco de las herramientas de otros navegadores, por lo que no he podido probarlo.

¿En cuál de estos tres casos recogerán IE9 + y Firefox la instancia someClass ?


La heurística varía, pero una forma común de implementar este tipo de cosas es crear un registro de entorno para cada llamada a f() en su caso, y solo almacenar los locales de f que en realidad están cerrados (por algún cierre) en ese entorno grabar. Luego, cualquier cierre creado en la llamada a f mantiene vivo el registro del entorno. Creo que así es como Firefox implementa los cierres, al menos.

Esto tiene los beneficios de un acceso rápido a las variables cerradas y la simplicidad de la implementación. Tiene el inconveniente del efecto observado, donde un cierre efímero que se cierra sobre alguna variable hace que se mantenga vivo con cierres de larga duración.

Uno podría tratar de crear múltiples registros de entorno para cierres diferentes, dependiendo de lo que realmente cierran, pero eso puede complicarse muy rápidamente y puede causar problemas de rendimiento y memoria propios ...


Por lo que puedo decir, esto no es un error sino el comportamiento esperado.

Desde la página de administración de memoria de Mozilla: "A partir de 2012, todos los navegadores modernos envían un recolector de basura de marcaje y barrido". "Limitación: los objetos deben estar explícitamente inalcanzables " .

En sus ejemplos donde falla, todavía se puede alcanzar algo en el cierre. Intenté dos formas de hacerlo inalcanzable y ambos funcionan. O establece some=null cuando ya no la necesita o establece window.f_ = null; y se habrá ido.

Actualizar

Lo probé en Chrome 30, FF25, Opera 12 e IE10 en Windows.

El standard no dice nada sobre la recolección de basura, pero da algunas pistas de lo que debería suceder.

  • Sección 13 Definición de función, paso 4: "Permita que el cierre sea el resultado de crear un nuevo objeto Function como se especifica en 13.2"
  • Sección 13.2 "un entorno léxico especificado por alcance" (alcance = cierre)
  • Sección 10.2 Entornos léxicos:

"La referencia externa de un entorno léxico (interno) es una referencia al entorno léxico que lógicamente rodea el entorno léxico interno.

Un entorno léxico externo puede, por supuesto, tener su propio entorno léxico externo. Un entorno léxico puede servir como el entorno externo para múltiples entornos léxicos internos. Por ejemplo, si una declaración de función contiene dos declaraciones de funciones anidadas , los entornos léxicos de cada una de las funciones anidadas tendrán como entorno léxico externo el entorno léxico de la ejecución actual de la función circundante ".

Entonces, una función tendrá acceso al entorno del padre.

Por lo tanto, some deberían estar disponibles en el cierre de la función de retorno.

Entonces, ¿por qué no está siempre disponible?

Parece que Chrome y FF son lo suficientemente inteligentes como para eliminar la variable en algunos casos, pero tanto en Opera como en IE la variable está disponible en el cierre (NB: para ver esto establece un punto de interrupción en return null y verifica el depurador).

El GC podría mejorarse para detectar si some se usa o no en las funciones, pero será complicado.

Un mal ejemplo:

var someClass = function() {}; function f() { var some = new someClass(); return function(code) { console.log(eval(code)); }; } window.f_ = f(); window.f_(''some'');

En el ejemplo anterior, el GC no tiene manera de saber si la variable se usa o no (se probó el código y funciona en Chrome30, FF25, Opera 12 e IE10).

La memoria se libera si la referencia al objeto se rompe asignando otro valor a window.f_ .

En mi opinión, esto no es un error.


Probé esto en IE9 + y Firefox.

function f() { var some = []; while(some.length < 1e6) { some.push(some.length); } function g() { some; } //removing this fixes a massive memory leak return function() {}; //or removing this } var a = []; var interval = setInterval(function() { var len = a.push(f()); if(len >= 500) { clearInterval(interval); } }, 10);

Sitio en vivo here .

Esperaba terminar con una matriz de 500 function() {} ''s, usando memoria mínima.

Desafortunadamente, ese no fue el caso. Cada función vacía se mantiene en una matriz (siempre inalcanzable, pero sin GC) de un millón de números.

Chrome finalmente se detiene y muere, Firefox termina todo después de usar casi 4 GB de RAM, y IE crece de manera asintótica más lenta hasta que muestra "Sin memoria".

Eliminar cualquiera de las líneas comentadas corrige todo.

Parece que estos tres navegadores (Chrome, Firefox e IE) mantienen un registro de entorno por contexto, no por cierre. Boris hipotetiza que la razón detrás de esta decisión es el rendimiento, y eso parece probable, aunque no estoy seguro de cómo se puede llamar a la luz del experimento anterior.

Si es necesario un cierre haciendo referencia a some (concede que no lo usé aquí, pero imagino que lo hice), si en lugar de

function g() { some; }

yo suelo

var g = (function(some) { return function() { some; }; )(some);

solucionará los problemas de memoria moviendo el cierre a un contexto diferente al de mi otra función.

Esto hará que mi vida sea mucho más tediosa.

PD. Por curiosidad, probé esto en Java (usando su capacidad para definir clases dentro de las funciones). GC funciona como originalmente esperaba Javascript.