Este articulo necesita una revisión editorial. Cómo puedes ayudar.
Esta traducción está incompleta. Por favor, ayuda a traducir este artículo del inglés.
Introducción
Los lenguajes de bajo nivel, como C, tienen primitivos de bajo nivel como malloc()
y free()
para la gestión de memoria. Por otro lado, los valores en JavaScript son reservados cuando los elementos (objetos, strings, etc.) son creados y "automáticamente" liberados cuando ya no son utilizados. El proceso anterior es conocido como Recolección de basura (garbage collection). Su forma "automática" es fuente de confusión, y da la impresión a los desarrolladores de JavaScript (y de otros lenguajes de alto nivel) de poder ignorar el proceso de gestión de memoria. Esto es erróneo.
Ciclo de vida de memoria
Sin importar el lenguaje de programación, el ciclo de memoria es casi siempre parecido al siguiente:
- Reservar la memoria necesaria
- Utilizarla (lectura, escritura)
- Liberar la memoria una vez ya no es necesaria.
El primer y el segundo paso son explícitos en todos los lenguajes. El último paso es explícito en lenguajes de bajo nivel, pero es mayormente implícito en lenguajes de alto nivel como JavaScript
Reserva de memoria en JavaScript
Inicialización de valores
Para no agobiar al programador con reservas de memoria, JavaScript las realiza al mismo tiempo que la declaración de los valores.
var n = 123; // reserva memoria para un número var s = "azerty"; // reserva memoria para un string var o = { a: 1, b: null }; // reserva memoria para un objeto y los valores que // contiene // (similar a objeto) reserva memoria para el arreglo y // los valores que contiene var a = [1, null, "abra"]; function f(a){ return a + 2; } // reserva una funcion (la cual es un objeto) // las expresiones de función tambien reservan un objeto someElement.addEventListener('click', function(){ someElement.style.backgroundColor = 'blue'; }, false);
Reserva de memoria al llamar una función
En ocasiones al llamar a una función se reserva un objeto.
var d = new Date(); // reserva un elemento del DOM var e = document.createElement('div');
Algunos métodos reservan nuevos valores u objetos:
var s = "azerty"; var s2 = s.substr(0, 3); // s2 es un nuevo string // Como los strings son valores inmutables, // JavaScript puede NO reservar memoria para este, // y solo almacenar el rango [0, 3]. var a = ["ouais ouais", "nan nan"]; var a2 = ["generation", "nan nan"]; var a3 = a.concat(a2); // nuevo arreglo con 4 elementos resultado de // concatenar los elementos de a y a2
Usando valores
Usar un valor es simplemente leerlo o escribirlo en memoria reservada. Esto puede ocurrir al leer o escribir el valor de una variable o de una propiedad de un objeto, inclusive pasando un argumento a una función.
Liberar la memoria cuando ya no es necesaria
En este punto ocurren la mayoria de los inconvenientes con la gestión de memoria. Lo más díficil es encontrar el cuándo la "memoria ya no es necesitada". En algunas ocaciones, se necesita que el desarrollador determine en qué parte de un programa esa parte de memoria ya no es necesitada y la libere.
Los lenguajes de alto nivel incluyen una herramienta de software conocida como "recolector de basura" (garbage collector), cuya función es rastrear las reservas de memoria para así encontrar cuándo cierta parte de la memoria ya no es necesaria, y en su momento liberarla automaticamente. Este proceso es sólo una aproximación al problema general de saber cuándo una parte de la memoria ya no es necesaria, ya que éste es indecidible (no puede ser resuelto por un algoritmo).
Recolección de basura (Garbage collection)
Como antes se mencionaba el problema general de encontrar automaticamente cuando la memoria "ya no es necesitada" es indecidible. Como consecuencia, las recolecciones de basura implementan sólo una restricción a la solución del problema general. En esta sección se explicarán las nociones necesarias para entender los algoritmos de recolección de basura y sus limitaciones.
Referencias
La noción primcipal de los algoritmos de recolección se basan en la noción de referencia. Dentro del contexto de gestión de memoria, se dice que un objeto referencia a otro si el primero tiene acceso al segundo ( ya sea de forma implícita o explícita ). Por ejemplo, un objeto de JavaScript guarda una referencia a su prototipo (referencia implícita) o a cualquiera de los valores de sus propiedades (referencia explícita)
Hay que mencionar que en este contexto la nocion de "objeto" se refiere tambien scopes de función (o el scope lexico global).
Recolección de basura a través de conteo de referencias
Éste es el algoritmo de recolección más simple. Este algoritmo reduce la definicion "un objejo ya no es necesario" a "un objeto ya no tiene ningun otro objeto que lo referencie". Un objeto es considerado recolectable si existen cero referencias hacia él.
Ejemplo
var o = { a: { b:2 } }; // Se crean dos objetos. Uno es referenciado por el otro como // una de sus propiedades. // El otro es referenciado al ser asignado a la variable "o" // Ninguno puede ser recolectado. var o2 = o; // la variable "o2" es lo segundo en tener una // referencia al objeto. o = 1; // ahora el objeto solo tiene una referencia mediante // la variable "o2" var oa = o2.a; // referencia a la propiedad "a" del objeto. // ahora el objeto posee dos referencias, una como propiedad // la otra como la variable "oa" o2 = "yo"; // el objeto original de "o" ahora ya no tiene // referencias a él. Pudiera ser recolecta. // Sin embargo lo que había en la propiedad "a" aun // esta refernciado en la variable "oa", // no puede ser recolectado aún oa = null; // lo que estaba en la propiedad "a" del objeto original en "o" // ahora ya no tiene ninguna referencia.Puede ser recolectado.
Limitación : ciclos
Este simple algoritmo tiene la limitación de que si un grupo de objetos se referencian a sí mismos (y forman un ciclo), nunca pasarán a "ya no ser necesitados" y no podŕan ser recolectados nunca.
function f(){ var o = {}; var o2 = {}; o.a = o2; // o referencía o2 o2.a = o; // o2 referencía o return "azerty"; } f(); // Dos objetos son creados y se referencían uno al otro creando un ciclo // Estan atrapados en el scope de la funcion después de la llamada // por lo que son inútiles fuera de la función y podrían ser recolectados. // Sin embargo, el algoritmo de conteo de referencias considera que como ambos objetos estan referenciados (aunque sean a si mismos) // ambos siguen en uso y por lo tanto no pueden ser recolectados.
Ejemplo real aplicado
Internet Explorer 6 y 7 son conocidos por tener recolectos de basura por conteo de referencias para los objetos del DOM. Los Ciclos son un error común que pueden generar memory leaks:
var div;
window.onload = function(){
div = document.getElementById("miDiv");
div.referenciaCircular = div;
div.muchosDatos = new Array(10000).join("*");
};
En el ejemplo anterior, el elemento del DOM "miDiv" posee una referencia circular asi mismo en la propiedad "referenciaCircular". Si la propiedad no es explicitamente removida o asignada con el valor null, un algoritmo de conteo de referencias siempre va a dejar por lo menos una referencia intacta y va a mantener el elemento del DOM activo incluso cuando es removido del DOM. Si el objeto del DOM contiene una gran cantidad de datos (ejemplificado en la propiedad "muchos datos"), la memoria consumida por estos datos nunca será liberada.
Algoritmo Mark-and-sweep
Este algoritmo reduce la definición de "un objeto ya no es necesitado" a "un objeto es inalcanzable"
Este algoritmo asume la noción de un grupo de objetos llamados objetos raíz(en JavaScript la raíz es el objeto global). Periodicamente el recolector empieza por estas raíces, encuentra todos los objetos que están referenciados por estas raíces, y luego todos los objetos referenciados de estos, etc. Empezando por las raíces, el recolector de esta forma va a encontrar todos los objetos que son alcanzables y a recolectar los que no.
Este algoritmo es mejor que el anterior ya que "un objeto tiene cero referencias" equivale al "objeto es inalcanzable". Esto no sucedía asi en el algoritmo anterior cuando se trataba de un ciclo.
Desde el 2012, todos los navegadores incluyen un recolector de basura basado en mark-and-sweep. Todas las mejoras realizadas en el campo de Recolección de basura en JavaScript (recolección generacional/incremental/concurrida/paralela) en los ultimos años son mejoras a la implementación del algoritmo pero no mejores sobre el sistema de recolección ni a su definicion de "un objeto ya no es necesitado".
Los ciclos son problema del pasado
En el primer ejemplo, después de que la llamada a una función termina, los dos objetos ya no son referenciados por nada alcanzable desde el objeto global. De esta forma van a ser identificados como inalcanzables por el recolector de basura.
Lo mismo ocurre en el segundo ejemplo. Una vez que el elemento div y sus metodos sean inalcanzable desde los objetos raíz, ambos pueden ser recolectados a pesar de que esten referenciados los unos de los otros.
Limitación: los objetos necesitan ser inalcanzables de forma explícita.
Aunque esto está marcado como una limitación, se puede encontrar muy poco en la práctica. Ésta es la razón por la cuál la recolección de basura es poco tomada en cuenta.