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 utilizan el generador de perfiles 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 ha recolectado).
En el siguiente código, la someClass
instancia es recolección de basura (buena):
var someClass = function() {};
function f() {
var some = new someClass();
return function() {};
}
window.f_ = f();
Pero no se recolectará basura en este caso (malo):
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 cualquier otro cierre hace referencia al objeto en el mismo contexto, ya sea que ese cierre sea incluso accesible.
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 montón de JavaScript, pero sé poco de las herramientas de otros navegadores, por lo que no he podido probar esto.
¿En cuál de estos tres casos IE9 + y Firefox recolectarán la someClass
instancia?
unreachable
función nunca se ejecuta, por lo que no se registra realmente nada.Respuestas:
Por lo que puedo decir, esto no es un error, sino el comportamiento esperado.
De la página de gestión de memoria de Mozilla : "A partir de 2012, todos los navegadores modernos envían un recolector de basura de marcado y barrido". "Limitación: los objetos deben hacerse explícitamente inalcanzables " .
En sus ejemplos donde falla
some
todavía se puede acceder en el cierre. Intenté dos formas de hacerlo inalcanzable y ambas funcionan. O lo configurasome=null
cuando ya no lo necesita, o lo configurawindow.f_ = null;
y desaparecerá.Actualizar
Lo he probado en Chrome 30, FF25, Opera 12 e IE10 en Windows.
El estándar no dice nada sobre la recolección de basura, pero da algunas pistas de lo que debería suceder.
Por lo tanto, una función tendrá acceso al entorno del padre.
Por lo tanto,
some
debe estar disponible en el cierre de la función de retorno.Entonces, ¿por qué no siempre está 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
some
variable está disponible en el cierre (NB: para ver esto, establezca un punto de interrupciónreturn null
y verifique el depurador).El GC podría mejorarse para detectar si
some
se usa o no en las funciones, pero será complicado.Un mal ejemplo:
En el ejemplo anterior, el GC no tiene forma de saber si la variable se usa o no (código probado 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.
fuente
setTimeout()
ejecuta la devolución de llamada, se completa el alcance de la función de lasetTimeout()
devolución de llamada y se debe recopilar todo ese alcance, liberando su referenciasome
. Ya no hay ningún código que pueda ejecutarse que pueda alcanzar la instanciasome
en el cierre. Debe ser basura recolectada. El último ejemplo es aún peor porqueunreachable()
ni siquiera se llama y nadie tiene una referencia. Su alcance debe ser GCed también. Estos dos parecen errores. No hay ningún requisito de lenguaje en JS para "liberar" cosas en un ámbito de función.f()
se llama al último , no hay referencias reales asome
más. Es inalcanzable y debe ser GCed.eval
es un caso realmente especial. Por ejemplo,eval
no puede tener alias ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), por ejemplovar eval2 = eval
. Sieval
se usa (y dado que no se puede llamar con un nombre diferente, eso es fácil de hacer), entonces debemos suponer que puede usar cualquier cosa dentro del alcance.Probé esto en IE9 + y Firefox.
Sitio en vivo aquí .
Esperaba terminar con una serie de 500
function() {}
's, usando un mínimo de memoria.Lamentablemente, ese no fue el caso. Cada función vacía se aferra a una matriz (de un alcance inalcanzable, pero no 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, e IE crece asintóticamente más lento hasta que muestra "Sin memoria".
Eliminar cualquiera de las líneas comentadas arregla todo.
Parece que estos tres navegadores (Chrome, Firefox e IE) mantienen un registro de entorno por contexto, no por cierre. Boris plantea la hipótesis de que la razón detrás de esta decisión es el rendimiento, y eso parece probable, aunque no estoy seguro de qué tan eficaz puede llamarse a la luz del experimento anterior.
Si necesita una referencia de cierre
some
(concedido, no lo usé aquí, pero imagino que lo hice), si en lugar deyo suelo
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, intenté esto en Java (usando su capacidad para definir clases dentro de las funciones). GC funciona como esperaba originalmente Javascript.
fuente
Las heurísticas varían, pero una forma común de implementar este tipo de cosas es crear un registro de entorno para cada llamada
f()
en su caso, y solo almacenar los localesf
que realmente están cerrados (por algún cierre) en ese registro de entorno. Luego, cualquier cierre creado en la llamada paraf
mantener vivo el registro del entorno. Creo que así es como Firefox implementa cierres, al menos.Esto tiene los beneficios del acceso rápido a variables cerradas y la simplicidad de implementación. Tiene el inconveniente del efecto observado, donde un cierre de corta duración por alguna variable hace que se mantenga vivo por los cierres de larga duración.
Uno podría intentar crear múltiples registros de entorno para diferentes cierres, dependiendo de lo que realmente cierren, pero eso puede complicarse muy rápidamente y puede causar problemas de rendimiento y memoria por sí mismo ...
fuente
O(n^2)
oO(2^n)
como una explosión, pero no como un aumento proporcional.como add (5); // devuelve 5
agregar (20); // devuelve 25 (5 + 20)
agregar (3); // devuelve 28 (25 + 3)
dos formas en que puede hacer esto primero es normal definir una variable global Por supuesto, puede usar una variable global para mantener el total. Pero ten en cuenta que este tipo te comerá vivo si (ab) usas globals.
ahora la última forma de usar el cierre sin definir la variable global
fuente
fuente
fuente