Está en la página 1de 4

[C#] ¿Cómo funciona el Garbage

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.

Veamos ejemplos de los dos últimos casos.

Para el primer ejemplo, simplemente tenemos el siguiente código:

1: for (int i = 0; i < 100000; i++)


2: {
3: var customObject = new CustomObject();
4: 
5: System.Console
6: .Write(string.Format("Iteration {0}; Memory {1}\n", i,
GC.GetTotalMemory(false)));
7: }

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:

1: for (int i = 0; i < 100000; i++)


2: {
3: var customObject = new CustomObject();
4: GC.Collect();
5: System.Console
6: .Write(string.Format("Iteration {0}; Memory {1}\n", i,
GC.GetTotalMemory(false)));
7: }

Y como resultado para esta muestra obtenemos la siguiente gráfica:

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);

Los objetos que no son reclamados al GC se llaman “supervivientes”. Si están en la generación 0 y no


han sido reclamados, pasan a la 1. Si están en la 2, siguen en la 2. El GC además detecta estos casos
como algo particular: si cada vez hay más supervivientes, balancea el algoritmo para lograr un equilibrio
entre el consumo de memoria y el tiempo de ejecución del mismo.

El algoritmo sigue el siguiente proceso:

 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:

Posted: 31/12/2013 16:39 por Andrés Pérez | con no comments


Archivado en: c#,garbage collector
Comparte este post:        

URL:

http://geeks.ms/blogs/aperez/archive/2013/12/31/c-191-c-243-mo-funciona-el-garbage-collector-
ii.aspx

También podría gustarte