stop loop ejemplo javascript memory-leaks setinterval

loop - ¿El método setInterval() de JavaScript causa pérdida de memoria?



setinterval vs settimeout (8)

Actualmente desarrollando un proyecto de animación basado en JavaScript.

Me he dado cuenta de que, el uso correcto de setInterval() , setTimeout() e incluso requestAnimationFrame asigna memoria sin mi solicitud, y causa llamadas frecuentes de recolección de basura. Más llamadas de GC = parpadeos :-(

Por ejemplo; cuando ejecuto el siguiente código simple llamando a init () en Google Chrome, la asignación de memoria + recolección de basura está bien durante los primeros 20-30 segundos ...

function init() { var ref = window.setInterval(function() { draw(); }, 50); } function draw() { return true }

De alguna manera, dentro de aproximadamente un minuto, ¡comienza un extraño aumento en la memoria asignada! Dado que se llama a init () solo por una vez, ¿cuál es la razón del aumento en el tamaño de la memoria asignada?

(Edición: captura de pantalla de chrome cargada)

NOTA # 1: Sí, he intentado llamar a clearInterval () antes del siguiente setInterval (). ¡El problema sigue siendo el mismo!

NOTA # 2: Para aislar el problema, mantengo el código anterior simple y estúpido.


Cada vez que realice una llamada de función, creará un marco de pila . A diferencia de muchos otros idiomas, Javascript almacena el marco de pila en el montón, como todo lo demás. Esto significa que cada vez que llama a una función, que está haciendo cada 50 ms, se agrega un nuevo marco de pila al montón. Esto se suma y eventualmente se recolecta la basura.

Es un poco inevitable, dado que Javascript funciona. Lo único que realmente se puede hacer para mitigarlo es hacer que los marcos de pila sean lo más pequeños posible, lo cual estoy seguro de que todas las implementaciones lo hacen.


Chrome apenas percibe presión en la memoria de su programa (1.23 MB es un uso de memoria muy bajo según los estándares actuales), por lo que probablemente no cree que necesite GC de forma agresiva. Si modifica su programa para usar más memoria, verá que el recolector de basura entra en acción. Por ejemplo, intente esto:

<!html> <html> <head> <title>Where goes memory?</title> </head> <body> Greetings! <script> function init() { var ref = window.setInterval(function() { draw(); }, 50); } function draw() { var ar = new Array(); for (var i = 0; i < 1e6; ++i) { ar.push(Math.rand()); } return true } init(); </script> </body> </html>

Cuando ejecuto esto, obtengo un patrón de uso de la memoria del diente de sierra, que se aproxima a unos 13.5 MB (de nuevo, bastante pequeño para los estándares actuales).

PS: Específicos de mis navegadores:

Google Chrome 23.0.1271.101 (Official Build 172594) OS Mac OS X WebKit 537.11 (@136278) JavaScript V8 3.13.7.5 Flash 11.5.31.5 User Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11


EDIT: La respuesta de Yury es mejor.

tl; dr IMO no hay pérdida de memoria. La pendiente positiva es simplemente el efecto de setInterval y setTimeout. La basura se recoge, como se ve en los patrones de diente de sierra, lo que significa que, por definición, no hay pérdida de memoria. (Yo creo que).

No estoy seguro de que haya una manera de evitar esta llamada "pérdida de memoria". En este caso, "pérdida de memoria" se refiere a cada llamada a la función setInterval que aumenta el uso de la memoria, como se ve por las pendientes positivas en el perfilador de memoria.

La realidad es que no hay una pérdida real de memoria: el recolector de basura todavía puede recolectar la memoria. La pérdida de memoria por definición "ocurre cuando un programa de computadora adquiere memoria pero no puede liberarla de nuevo al sistema operativo".

Como lo muestran los perfiles de memoria a continuación, la pérdida de memoria no está ocurriendo. El uso de la memoria está aumentando con cada llamada de función. El OP espera que debido a que esta es la misma función que se llama una y otra vez, no debería haber un aumento de memoria. Sin embargo, éste no es el caso. La memoria se consume con cada llamada de función. Eventualmente, la basura se recolecta, creando el patrón de diente de sierra.

He explorado varias formas de reorganizar los intervalos, y todos llevan al mismo patrón de diente de sierra (aunque algunos intentos llevan a que nunca se realice la recolección de basura ya que se retuvieron las referencias).

function doIt() { console.log("hai") } function a() { doIt(); setTimeout(b, 50); } function b() { doIt(); setTimeout(a, 50); } a();

http://fiddle.jshell.net/QNRSK/14/

function b() { var a = setInterval(function() { console.log("Hello"); clearInterval(a); b(); }, 50); } b();

http://fiddle.jshell.net/QNRSK/17/

function init() { var ref = window.setInterval(function() { draw(); }, 50); } function draw() { console.log(''Hello''); } init();

http://fiddle.jshell.net/QNRSK/20/

function init() { window.ref = window.setInterval(function() { draw(); }, 50); } function draw() { console.log(''Hello''); clearInterval(window.ref); init(); } init();​

http://fiddle.jshell.net/QNRSK/21/

Al parecer, setTimeout y setInterval no son oficialmente partes de Javascript (por lo tanto, no son parte de v8). La implementación se deja al implementador. Le sugiero que eche un vistazo a la implementación de setInterval y demás en node.js


El problema aquí no está en el código en sí, no se filtra. Es por la forma en que se implementa el panel Timeline. Cuando la línea de tiempo registra eventos, recopilamos trazas de la pila de JavaScript en cada invocación de la devolución de llamada setInterval. El seguimiento de la pila se asigna primero en el montón JS y luego se copia en las estructuras de datos nativas, una vez que el seguimiento de la pila se copia en el evento nativo se convierte en basura en el montón JS. Esto se refleja en la gráfica. Deshabilitar la siguiente llamada http://trac.webkit.org/browser/trunk/Source/WebCore/inspector/TimelineRecordFactory.cpp#L55 hace que el gráfico de memoria sea plano.

Hay un error relacionado con este problema: https://code.google.com/p/chromium/issues/detail?id=120186


Intenta hacer esto sin la función anónima. Por ejemplo:

function draw() { return true; } function init() { var ref = window.setInterval(draw, 50); }

¿Todavía se comporta de la misma manera?


No parece haber una pérdida de memoria. Mientras el uso de la memoria vuelva a disminuir después del GC, y el uso de la memoria general no tenga una tendencia ascendente en promedio, no habrá fugas.

La pregunta "real" que estoy viendo aquí es que setInterval realmente usa la memoria para operar, y no parece que deba estar asignando nada. En realidad necesita asignar algunas cosas:

  1. Necesitará asignar algo de espacio de pila para ejecutar tanto la función anónima como la rutina draw ().
  2. No sé si es necesario asignar datos temporales para realizar las llamadas por sí mismos (probablemente no)
  3. Debe asignar una pequeña cantidad de almacenamiento para mantener ese true valor de retorno de draw() .
  4. Internamente, setInterval puede asignar memoria adicional para volver a programar un evento recurrente (no sé cómo funciona internamente, puede reutilizar el registro existente).
  5. El JIT puede intentar rastrear ese método, que asignaría almacenamiento adicional para la traza y algunas métricas. La VM puede determinar que este método es demasiado pequeño para rastrearlo, no sé exactamente cuáles son todos los umbrales para activar o desactivar el rastreo. Si ejecuta este código el tiempo suficiente para que la VM lo identifique como "caliente", puede asignar aún más memoria para contener el código de máquina compilado JIT (después de lo cual, esperaría que el uso promedio de memoria disminuya, porque el código de máquina generado debería asignar menos memoria en la mayoría de los casos)

Cada vez que se ejecute su función anónima habrá memoria asignada. Cuando esas asignaciones se suman a un cierto umbral, el GC se activará y limpiará para llevarlo de vuelta a un nivel base. El ciclo continuará así hasta que lo apague. Este es el comportamiento esperado.


Quería responder a tu comentario sobre setInterval y parpadeo:

Me he dado cuenta de que, el uso correcto de setInterval (), setTimeout () e incluso requestAnimationFrame asigna memoria sin mi solicitud, y causa llamadas frecuentes de recolección de basura. Más llamadas de GC = parpadeos :-(

Es posible que desee intentar reemplazar la llamada a setInterval con una función de auto-invocación menos perversa basada en setTimeout. Paul Irish menciona esto en la charla llamada 10 cosas que aprendí de la fuente de jQuery (el video here , las notas here ver # 2). Lo que hace es reemplazar su llamada a setInterval con una función que se invoca indirectamente a través de setTimeout una vez que completa el trabajo que debe hacer . Para citar la charla:

Muchos han argumentado que setInterval es una función maligna. Sigue llamando a una función a intervalos específicos, independientemente de si la función está terminada o no.

Usando su código de ejemplo anterior, puede actualizar su función init desde:

function init() { var ref = window.setInterval(function() { draw(); }, 50); }

a:

function init() { //init stuff //awesome code //start rendering drawLoop(); } function drawLoop() { //do work draw(); //queue more work setTimeout(drawLoop, 50); }

Esto debería ayudar un poco porque:

  1. draw () no será llamado nuevamente por tu bucle de renderización hasta que se complete
  2. Como señalan muchas de las respuestas anteriores, todas las llamadas de función ininterrumpidas de setInterval suponen una sobrecarga para el navegador.
  3. la depuración es un poco más fácil ya que no se ve interrumpido por la activación continua de setInterval

¡Espero que esto ayude!


También tengo el mismo problema. El cliente me informó que la memoria de su computadora aumentaba cada vez más y más. Al principio pensé que era realmente extraño que una aplicación web pudiera hacer eso a pesar de que se accedía mediante un simple navegador. Noté que esto sucedió solo en Chrome.

Sin embargo, comencé con un socio para investigar y, a través de las herramientas de desarrollo de Chrome y la tarea del administrador, pudimos ver el aumento de memoria que el cliente me había informado.

Luego vemos que se cargó una función jquery (marco de animación de solicitud) una y otra vez aumentando la memoria del sistema. Después de eso, vimos que gracias a esta publicación, una cuenta regresiva de jquery estaba haciendo eso, porque tiene dentro de un "SETINTERVAL" que cada vez actualizaba la fecha en el diseño de mi aplicación.

Mientras trabajo con ASP.NET MVC, acabo de salir de esta cuenta atrás del script jquery del BundleConfig y de mi diseño, reemplazando mi cuenta atrás del tiempo con el siguiente código:

@(DateTime.Now.ToString("dd/MM/yyyy HH:mm"))