vtortola.Net

Julio 10, 2007

Jugando con el Garbage Collector

Archivado en: .NET, C#, Garbage Collector — vtortola @ 7:50 pm

Ya había hablado del Garbage Collector aquí antes, pero me surgieron dudas existenciales sobre que pasa con una clase y sus relacionadas cuando se trata de una composición y cuando se trata de una agregación, que pasa con los hijos… con los nietos… y cuestiones varias como ¿y si se intenta destruir un hijo cuyo padre esta desreferenciado pero el GC aún no lo ha recolectado? ¿mantendría esta situación al hijo “vivo” hasta la siguiente recolección? etc… las típicas cosas que te hacen comerte la cabeza, así que decidí hacer unos experimentos :D La conclusión es simple, si el GC sale de caza y todos se han olvidado de ti… ó los que se acuerdan de ti ya nadie se acuerda de ellos… te come el GC (que trágica es la vida de un objeto en .Net :D ).

En principio, cuando una relación entre dos objetos es de composición, digamos que el hijo no puede vivir sin el padre, por lo que al destruir el padre ha de destruirse el hijo; cuando se trata de una relación de agregación, el hijo puede subsistir sin el padre ya que podría pertenecer a más relaciones ó simplemente ser un objeto independiente. Pero, estos dos conceptos son únicamente inherentes al diseño, en la práctica un objeto es un objeto y si no existe una referencia a él en ningún sitio es candidato a ser destruido por el GC, da igual de que color pintases la flecha en el diagrama UML xD.

Para las pruebas usaré esta sencilla clase de nodo, que permite ponerle un nombre, añadir más subnodos e indica en la consola cuando es destruida la instancia, que nombre tiene y en que generación ha sido:

class Nodo
{

string name;
List<Nodo> items;
public string Name
{

get
{

return name;

}

}
public List<Nodo> Items
{

get
{

return items;

}

}
public Nodo(string Name)
{

this.name = Name;
items =
new List<Nodo>();
Prueba =
new TestClass(Name);

}
~Nodo()
{

Console.WriteLine(“Destruyendo nodo {0} en generación {1}”,this.name, GC.GetGeneration(this));

}

}

Si creo un arbol de nodos y a continuación desreferencio al padre…

Nodo Padre = new Nodo(“Padre”);
Padre.Items.Add(
new Nodo(“Hijo 1″));
Padre.Items.Add(
new Nodo(“Hijo 2″));
Padre.Items.Add(
new Nodo(“Hijo 3″));
Padre.Items[0].Items.Add(
new Nodo(“Nieto 1″));
Padre.Items[0].Items.Add(
new Nodo(“Nieto 2″));
Padre.Items[0].Items.Add(
new Nodo(“Nieto 3″));
Padre =
null;
GC.Collect();

Sucede que todos son destruidos:

Destruyendo Hijo 2 en generación 0
Destruyendo Padre en generación 0
Destruyendo Nieto 3 en generación 0
Destruyendo Nieto 2 en generación 0
Destruyendo Nieto 1 en generación 0
Destruyendo Hijo 3 en generación 0
Destruyendo Hijo 1 en generación 0


Pero, si creo una referencia a por ejemplo el “Nieto 1”…

Nodo Padre = new Nodo(“Padre”);
Padre.Items.Add(
new Nodo(“Hijo 1″));
Padre.Items.Add(
new Nodo(“Hijo 2″));
Padre.Items.Add(
new Nodo(“Hijo 3″));
Padre.Items[0].Items.Add(
new Nodo(“Nieto 1″));
Padre.Items[0].Items.Add(
new Nodo(“Nieto 2″));
Padre.Items[0].Items.Add(
new Nodo(“Nieto 3″));

Nodo Nieto1 = Padre.Items[0].Items[0];

Padre = null;
GC.Collect();

Sucede que ese nodo en concreto no es recolectado, independientemente de que la clase que lo contenía haya desaparecido ya.

Destruyendo Nieto 2 en generación 2
Destruyendo Hijo 3 en generación 2
Destruyendo Hijo 2 en generación 2
Destruyendo Hijo 1 en generación 2
Destruyendo Padre en generación 2
Destruyendo Nieto 3 en generación 2

Esto se extiende a cualquier tipo de objeto, no solo a los que están en colecciones. Por ejemplo, creo una nueva clase que pueda monitorizar su destrucción:

class TestClass
{

string name;
public TestClass(string Name)
{

this.name = Name;

}
~TestClass()
{

Console.WriteLine(“Destruyendo TestClass 0 en generación 1″, this.name, GC.GetGeneration(this));

}

}

La añado como miembro a la clase Nodo, quedando de esta forma:

class Nodo
{

string name;
List<Nodo> items;
public TestClass Prueba;
public string Name
{

get
{

return name;

}

}
public List<Nodo> Items
{

get
{

return items;

}

}
public Nodo(string Name)
{

this.name = Name;
items =
new List<Nodo>();
Prueba =
new TestClass(Name);

}
~Nodo()
{

Console.WriteLine(“Destruyendo nodo {0} en generación {1}”,this.name, GC.GetGeneration(this));

}

}

Si ejecuto de nuevo el primer ejemplo, todo se destruye:

Destruyendo nodo Hijo 2 en generación 1
Destruyendo TestClass Nieto 1 en generación 1
Destruyendo nodo Nieto 1 en generación 1
Destruyendo TestClass Hijo 3 en generación 1
Destruyendo nodo Hijo 3 en generación 1
Destruyendo TestClass Hijo 2 en generación 1
Destruyendo TestClass Hijo 1 en generación 1
Destruyendo nodo Hijo 1 en generación 1
Destruyendo TestClass Padre en generación 1
Destruyendo nodo Padre en generación 1
Destruyendo TestClass Nieto 3 en generación 1
Destruyendo nodo Nieto 3 en generación 1
Destruyendo TestClass Nieto 2 en generación 1
Destruyendo nodo Nieto 2 en generación 1

En conclusión, el algoritmo de optimización de memoria del GC contempla estas situaciones, y todo lo que no pueda ser accedido ya sea de forma directa ó indirecta es recolectado.

Hasta el próximo capítulo de “El Garbage Collector y tu” ;)

2 comentarios »

  1. [...] puestos en temas del GC, hablemos del patrón desechable, que aunque nos permite crear clases que sean de usar y tirar… [...]

    Pingback por vtortola : El patrón desechable con la interfaz IDisposable — Julio 17, 2007 @ 7:19 pm

  2. [...] puestos en temas del GC, hablemos del patrón desechable, que aunque nos permite crear clases que sean de usar y tirar… [...]

    Pingback por El patrón desechable con la interfaz IDisposable - vtortola — Julio 17, 2007 @ 7:24 pm


Canal RSS de los comentarios de la entrada. URI para TrackBack.

Deja un comentario

Debes ser Sesión como para publicar un comentario.

Blog de WordPress.com.