Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Collector? ( II )
En el artículo anterior introduje brevemente qué mecanismos disponemos para liberar los recursos de los
objetos y cómo actúa GC sobre ellos. Normalmente para un desarrollador GC es algo que está ahí pero
no necesita trastear. Pero para aplicaciones más avanzadas, sistemas críticos o debug/testeo suele ser
muy útil.
Liberando memoria
El GC tiene la función de liberar la memoria de los objetos. Esto puede ocurrir en los siguiente casos:
Limitaciones técnicas de la máquina: Tenemos demasiada memoria ocupada y alguien tiene que
liberarla. Si es nuestra, el GC se encargará de ello.
Los objetos que tenemos en el Heap se acumulan y excede el máximo permitido. El GC se
dispara y recolecta.
O directamente, porque lo invocamos explícitamente.
Donde tenemos un bucle que va instanciando los objetos. Al instanciar un objeto, se reserva la memoria
necesaria para él y se crea la instancia. Ese objeto permanecerá en memoria hasta que el GC estime
oportuno. Vamos a ver cómo a medida que se incrementa el número de iteraciones la memoria asignada
por el GC irá aumentando hasta que decida liberarla.
En el segundo ejemplo, forzamos a través de GC.Collect() que cada vez que se crea el objeto, invocamos
al GC para que recorra todo su grafo de objetos y elimine aquellos que no se van a usar:
Donde podemos observar como en el primer ejemplo el GC acumula memoria hasta que la libera según
estima oportuno mientras que en el segundo caso la cantidad de memoria ocupada permanece estable.
Evidentemente esta recolecta forzada no sale gratis, veamos ahora en escala de tiempo cuánto
repercute. Como el coste por iteración es despreciable, a continuación muestro qué ocurre si invocamos
el GC o no en bloques de iteraciones:
Podemos ver una diferencia de tiempo de orden exponencial. Es decir, debemos seguir las
recomendaciones dadas de dejar que GC trabaje y nosotros no interactuar con él salvo que tengamos
razones muy justificadas para ello.
Generaciones
Una vez que hemos visto cuándo se dispara y qué ocurre cuando se dispara, vamos a ver cómo esta
distribuido. Aquí introduzco el tema de “Generaciones”, que es la estructura de distribución de objetos
que dispone GC:
Generación 0: Contiene los objetos que menos duran, como variables temporales. Inicialmente
todo objeto irá directamente a este nivel de generación.
Generación 1: Contiene objetos de corta duración y actúa de buffer entre objetos de corta y
larga duración.
Generación 2: Contiene objetos de larga duración. Por ejemplo, variables estáticas a nivel de
aplicación.
Todo objeto en función de su uso y otros factores, se van moviendo en las respectivas generaciones.
Como ejemplo se puede ver cómo se obtiene la generación de un determinada instancia:
1: GC.GetGeneration(customObject);
Esto devuelve 0,1 ó 2 en función de la generación de esa instancia. Además de el objeto en sí, podemos
pasarle una WeakReference directamente.
También podemos conocer cuántas veces se ha invocado el GC en una generación concreta a través de
este método:
1: GC.CollectionCount(0);
En una primera fase se busca y se crea una lista con todos los objetos vivos.
Después se actualizan las referencias de los objetos y se relocalizan.
Por último, una fase de compactación de memoria que agrupa el espacio disponible junto con el
liberado por los objetos que ya no existen. La compactación siempre se hará salvo para aquellos
casos en que los objetos sean demasiado grandes. Se puede recurrir a esta propiedad del GC
para forzar que los objetos grandes siempre se compacten (sólo disponible a partir del 4.5.1).
Antes de que el GC se dispare, todos los hilos de ejecución de la aplicación se suspenden para poder
activarse el hilo dedicado al GC. En ese momento el GC aplica el algoritmo y el proceso mencionado
anteriormente. Cuando termina, el resto de hilos prosiguen en su ejecución:
URL:
http://geeks.ms/blogs/aperez/archive/2013/12/31/c-191-c-243-mo-funciona-el-garbage-collector-
ii.aspx