¿Qué es la recolección de basura de JavaScript?

Respuestas:

192

Eric Lippert escribió una publicación de blog detallada sobre este tema hace un tiempo (comparándolo adicionalmente con VBScript ). Más exactamente, escribió sobre JScript , que es la propia implementación de ECMAScript de Microsoft, aunque muy similar a JavaScript. Me imagino que puede asumir que la gran mayoría del comportamiento sería el mismo para el motor de JavaScript de Internet Explorer. Por supuesto, la implementación variará de un navegador a otro, aunque sospecho que podría tomar una serie de principios comunes y aplicarlos a otros navegadores.

Citado de esa página:

JScript utiliza un recolector de basura no generacional de marcado y barrido. Funciona así:

  • Cada variable que está "dentro del alcance" se llama "carroñero". Un carroñero puede referirse a un número, un objeto, una cadena, lo que sea. Mantenemos una lista de carroñeros: las variables se trasladan a la lista de scav cuando entran en el alcance y fuera de la lista de scav cuando salen del alcance.

  • De vez en cuando se ejecuta el recolector de basura. Primero pone una "marca" en cada objeto, variable, cadena, etc. - toda la memoria rastreada por el GC. (JScript utiliza la estructura de datos VARIANT internamente y hay muchos bits adicionales no utilizados en esa estructura, por lo que solo configuramos uno de ellos).

  • Segundo, borra la marca en los carroñeros y el cierre transitivo de las referencias del carroñero. Entonces, si un objeto carroñero hace referencia a un objeto no carroñero, entonces limpiamos los bits en el no carroñero y en todo lo que se refiere. (Estoy usando la palabra "cierre" en un sentido diferente que en mi publicación anterior).

  • En este punto, sabemos que toda la memoria aún marcada es memoria asignada a la que ninguna ruta puede acceder desde ninguna variable dentro del alcance. Todos esos objetos tienen instrucciones de destruirse, lo que destruye cualquier referencia circular.

El objetivo principal de la recolección de basura es permitir que el programador no se preocupe por la administración de la memoria de los objetos que crea y usa, aunque, por supuesto, a veces no hay que evitarlo; siempre es beneficioso tener al menos una idea aproximada de cómo funciona la recolección de basura .

Nota histórica: una revisión anterior de la respuesta tenía una referencia incorrecta al deleteoperador. En JavaScript, el deleteoperador elimina una propiedad de un objeto y es completamente diferente a deleteC / C ++.

Noldorin
fuente
27
la guía de Apple tiene fallas: el autor usa deleteincorrectamente; por ejemplo, en el primer ejemplo, en lugar de delete foo, primero debe eliminar el detector de eventos mediante window.removeEventListener()y luego usar foo = nullpara sobrescribir la variable; en IE, delete window.foo(pero no delete foo) también habría funcionado si foofuera global, pero incluso así no funcionaría en FF u Opera
Christoph
3
Tenga en cuenta que el artículo de Eric debe considerarse "solo con fines históricos". Pero sigue siendo informativo.
Peter Ivan
2
También tenga en cuenta que IE 6 y 7 NO utilizan un recolector de basura no generacional de marcado y barrido. Utilizan un recolector de basura de conteo de referencia simple, que es más vulnerable a problemas de referencia circular con la recolección de basura.
Doug
1
El ECMAScript deletees un operador unario (una expresión), no una declaración (es decir:) delete 0, delete 0, delete 3. Parece una declaración cuando se expresa mediante una declaración de expresión.
Hydroper
Sí, la respuesta en ese momento ahora está desactualizada, a partir de 2012, los navegadores modernos usan un algoritmo de marca / barrido ... por lo que ya no depende del alcance. Referencia: developer.mozilla.org/en-US/docs/Web/JavaScript/…
sksallaj
52

Tenga cuidado con las referencias circulares cuando los objetos DOM están involucrados:

Patrones de pérdida de memoria en JavaScript

Tenga en cuenta que la memoria solo se puede recuperar cuando no hay referencias activas al objeto. Este es un error común con los cierres y los controladores de eventos, ya que algunos motores JS no verificarán qué variables se referencian realmente en las funciones internas y solo mantendrán todas las variables locales de las funciones de cierre.

Aquí hay un ejemplo simple:

function init() {
    var bigString = new Array(1000).join('xxx');
    var foo = document.getElementById('foo');
    foo.onclick = function() {
        // this might create a closure over `bigString`,
        // even if `bigString` isn't referenced anywhere!
    };
}

Una ingenua implementación de JS no se puede recopilar bigStringmientras el controlador de eventos esté disponible. Hay varias formas de resolver este problema, por ejemplo, configurar bigString = nullal final de init()( deleteno funcionará para variables locales y argumentos de función: deleteelimina las propiedades de los objetos, y el objeto variable es inaccesible - ES5 en modo estricto incluso arrojará un ReferenceErrorsi lo intentas para eliminar una variable local!).

Recomiendo evitar los cierres innecesarios tanto como sea posible si le importa el consumo de memoria.

Christoph
fuente
20
El error de referencia circular DOM es específico de JScript; ningún otro navegador lo padece excepto IE. De hecho, estoy bastante seguro de que la especificación ECMAScript señala expresamente que la GC debe ser capaz de manejar este tipo de ciclos: - /
olliej
@olliej: No veo ninguna mención del GC en la especificación ECMAScript .
Janus Troelsen
ver también point.davidglasser.net/2013/06/27/…
Christoph
16

Buena cita tomada de un blog

El componente DOM es "recolección de basura", al igual que el componente JScript, lo que significa que si crea un objeto dentro de cualquiera de los componentes y luego pierde el rastro de ese objeto, eventualmente se limpiará.

Por ejemplo:

function makeABigObject() {
var bigArray = new Array(20000);
}

Cuando llama a esa función, el componente JScript crea un objeto (llamado bigArray) al que se puede acceder dentro de la función. Sin embargo, tan pronto como la función regrese, "perderá el rastro" de bigArray porque ya no hay forma de referirse a ella. Bueno, el componente JScript se da cuenta de que has perdido el rastro y, por lo tanto, bigArray se limpia y recupera su memoria. El mismo tipo de cosas funciona en el componente DOM. Si dice document.createElement('div')algo similar, entonces el componente DOM crea un objeto para usted. Una vez que pierda el rastro de ese objeto de alguna manera, el componente DOM limpiará lo relacionado.

TStamper
fuente
13

Que yo sepa, los objetos de JavaScript son basura recolectada periódicamente cuando no quedan referencias al objeto. Es algo que sucede automáticamente, pero si desea ver más acerca de cómo funciona, a nivel C ++, tiene sentido echar un vistazo al código fuente de WebKit o V8

Por lo general, no necesita pensarlo, sin embargo, en los navegadores más antiguos, como IE 5.5 y versiones anteriores de IE 6, y tal vez las versiones actuales, los cierres crearían referencias circulares que, cuando no se controlan, terminarían consumiendo memoria. En el caso particular al que me refiero sobre cierres, fue cuando agregaste una referencia de JavaScript a un objeto dom, y un objeto a un objeto DOM que se refirió al objeto JavaScript. Básicamente, nunca se pudo recopilar y, con el tiempo, el sistema operativo se volvería inestable en las aplicaciones de prueba que se creaban bloqueos. En la práctica, estas filtraciones suelen ser pequeñas, pero para mantener limpio el código, debe eliminar la referencia de JavaScript al objeto DOM.

Por lo general, es una buena idea usar la palabra clave delete para desreferenciar de inmediato objetos grandes como datos JSON que ha recibido y ha hecho lo que sea necesario, especialmente en el desarrollo web móvil. Esto hace que el próximo barrido del GC elimine ese objeto y libere su memoria.

Avaro de calor
fuente
¿Se solucionó el problema de referencia circular JavaScript -> DOM -> JavaScript en versiones más recientes de IE? Si es así, ¿desde cuándo? Pensé que era arquitectónicamente muy profundo y que era poco probable que lo arreglaran. ¿Tienes alguna fuente?
erikkallen
Solo anecdóticamente. No he notado las fugas locas en IE 8 ejecutándose en modo estándar, no en el modo roto. Ajustaré mi respuesta.
Heat Miser
1
@erikkallen: sí, el error de GC se ha corregido en las versiones IE 8+, ya que los más antiguos usaban un algoritmo de recolección de basura muy ingenuo, lo que hacía imposible GC un par de objetos que se referían entre sí. Los mark-and-sweepalgoritmos de estilo más nuevos se encargan de esto .
kumarharsh
6

La recolección de basura (GC) es una forma de administración automática de memoria al eliminar los objetos que ya no se necesitan.

cualquier proceso de proceso con memoria sigue estos pasos:

1 - asigna el espacio de memoria que necesitas

2 - haz algo de procesamiento

3 - libera este espacio de memoria

Hay dos algoritmos principales utilizados para detectar qué objetos ya no son necesarios.

Recolección de basura de conteo de referencias : este algoritmo reduce la definición de "un objeto ya no es necesario" a "un objeto no tiene ningún otro objeto que haga referencia a él", el objeto se eliminará si no tiene ningún punto de referencia

Algoritmo de marca y barrido : conecta cada objeto a la fuente raíz. cualquier objeto no se conecta a la raíz u otro objeto. Este objeto será eliminado.

Actualmente, los navegadores más modernos utilizan el segundo algoritmo.

Ahmed Gaber - Biga
fuente
1
Y para agregar una fuente de esto, vea el MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/…
Xenos
4

"En informática, la recolección de basura (GC) es una forma de gestión automática de la memoria. El recolector de basura, o simplemente recolector, intenta recuperar la basura o la memoria utilizada por objetos a los que la aplicación nunca volverá a acceder o mutará".

Todos los motores de JavaScript tienen sus propios recolectores de basura, y pueden diferir. La mayoría de las veces no tiene que lidiar con ellos porque simplemente hacen lo que se supone que deben hacer.

Escribir un mejor código depende principalmente de qué tan bueno conoces los principios de programación, el lenguaje y la implementación particular.

mtasic85
fuente
1

¿Qué es la recolección de basura de JavaScript?

mira esto

¿Qué es importante que un programador web comprenda sobre la recolección de basura de JavaScript para poder escribir un código mejor?

En Javascript no te importa la asignación de memoria y la desasignación. Todo el problema se exige al intérprete de Javascript. Las fugas aún son posibles en Javascript, pero son errores del intérprete. Si está interesado en este tema, puede leer más en www.memorymanagement.org

dfa
fuente
¿Cuál de los diversos sistemas de administración de memoria en el artículo al que se vincula es el utilizado por JavaScript? "Las fugas aún son posibles en Javascript, pero son errores del intérprete". - Eso no significa que los programadores de JS simplemente puedan ignorar todo el problema, por ejemplo, hay un problema de referencia circular JS <-> DOM bastante conocido en versiones anteriores de IE que podría solucionar en su código JS. Además, la forma en que funcionan los cierres JS es una característica de diseño, no un error, pero puede atar fragmentos de memoria más grandes de lo previsto si usa cierres "inapropiadamente" ( no digo que no los use).
nnnnnn
3
Las pérdidas de memoria son una bestia en JavaScript. Si está escribiendo una aplicación simple de "proyecto universitario", entonces no se preocupe. Pero cuando comienza a escribir aplicaciones de nivel empresarial de alto rendimiento, la administración de memoria en JavaScript es imprescindible.
Doug
1

En Windows, puede usar Drip.exe para encontrar pérdidas de memoria o verificar si su rutina de memoria libre funciona.

Es realmente simple, solo ingrese la URL de un sitio web y verá el consumo de memoria del procesador integrado de IE. Luego presione actualizar, si la memoria aumenta, encontró una pérdida de memoria en algún lugar de la página web. Pero esto también es muy útil para ver si las rutinas para liberar memoria funcionan para IE.

Powtac
fuente
1

Los tipos de referencia no almacenan el objeto directamente en la variable a la que está asignado, por lo que la variable de objeto en este ejemplo en realidad no contiene la instancia del objeto. En cambio, contiene un puntero (o referencia) a la ubicación en la memoria donde existe el objeto

var object = new Object();

si asigna una variable a otra, cada variable obtiene una copia del puntero, y ambas aún hacen referencia al mismo objeto en la memoria.

var object1 = new Object();
var object2 = object1;

Dos variables apuntando a un objeto.

JavaScript es un lenguaje recolectado de basura , por lo que realmente no necesita preocuparse por las asignaciones de memoria cuando utiliza tipos de referencia. Sin embargo, es mejor desreferenciar objetos que ya no necesita para que el recolector de basura pueda liberar esa memoria. La mejor manera de hacer esto es establecer la variable del objeto en nulo.

var object1 = new Object();
// do something
object1 = null; // dereference

Desreferenciar objetos es especialmente importante en aplicaciones muy grandes que usan millones de objetos.

de Los principios de JavaScript orientado a objetos - NICHOLAS C. ZAKAS

Ani Naslyan
fuente