Está en la página 1de 5

Gestin de la memoria en .

NET y garbage collector (II)

En este artculo terminaremos de revisar algunos de los conceptos ms importantes que ataen al GC en .NET: Finalizacin, Generaciones y manipulacin del GC. Finalizacin

Ntese que decimos finalizadores y no destructores, como en terminologa C++. Esto se debe a que no se corresponde al mismo concepto, ya que el montculo se gestiona de una forma muy diferente y la finalizacin no es determinstica.

Existen objetos que requieren de ciertas tareas de limpieza previamente a la liberacin por parte del GC. Esto es cierto en contadas y muy concretas ocasiones, pues en la mayora de los casos la liberacin de un objeto en memoria es perfectamente posible sin intervencin explcita por parte del usuario.

Para esto existen mtodos de finalizacin como Finalize() y Dispose() (o Close()). No explicar en detalle lo que hacen estos mtodos; simplemente apuntar que la diferencia entre el primero y el segundo es que no sabemos con certeza en qu momento se invocar un Finalize() (cuando el GC libere memoria), mientras que un Dispose() fuerza a liberar el recurso referido. Adems, el uso de Dispose implica que el objeto referido debe implementar la interfaz IDisposable.

La diferencia entre Dispose() y Close() es ms conceptual que funcional. Se suele utilizar Dispose cuando el objeto no se va a utilizar ms definitivamente. Un ejemplo muy comn: los elementos grficos tales como controles y formularios WinForms); mientras que un Close indica que el objeto puede volver a utilizarse pero de momento se libera. Un ejemplo seran las conexiones a bases de datos o las conexiones de red.

Existe una sentencia en C# muy til que permite utilizar un recurso en un mbito muy concreto y liberarlo al final. sta es la sentencia using: using (MyResource res = new MyResource()) {

//uso del recurso }

Cuando el recurso sale del mbito de la sentencia using, automticamente se invoca su mtodo Dispose que libera el recurso. Esto implica que el objeto debe implementar la interfaz IDisposable, y por tanto implementar un mtodo Dispose. La sentencia using equivale al uso de un try-finally como el siguiente: MyResource res; try { res = new MyResource(); //uso del recurso } finally { res.Dispose(); }

Sin embargo, hay que anotar que aunque se invoque explcitamente el mtodo Dispose de un objeto, ste puede no ser liberado inmediatamente. Como dijimos en el artculo anterior, la liberacin de memoria en .NET no es determinstica.

Tambin existe un fenmeno curioso denominado resurreccin y que consiste en que un objeto puede volver a memoria despus de haberse recolectado. Para ms informacin al respecto, les remito a las referencias que incluyo al final de este artculo. Generaciones

En la prctica, los objetos se almacenan en el montculo segn ciertas categoras . Estas categoras se basan en las siguientes premisas, que en la mayorade los programas se cumplen:

Cuanto ms nuevo es un objeto, ms corta ser su vida. Cuanto ms viejo es un objeto, ms larga ser su vida. Recolectar una porcin del montculo es ms rpido que recolectar todo el montculo.

Cuando se inicializa el montculo, todos los objetos que se van aadiendo a dicha estructura conforman lo que se conoce como generacin 0. El CLR inicializa dicha generacin con un tamao determinado; digamos que ese tamao es 256Kb.

Cuando el montculo se queda sin espacio para albergar ms objetos, se ejecuta el GC. Los objetos que superan la criba del GC pasan a formar parte de la generacin 1. Este sector (por llamarlo de alguna manera) del montculo tiene un tamao ms grande; digamos que de 2Mb.

Los nuevos objetos que se crean a partir de este momento se almacenan en la generacin 0. Cuando la generacin 0 se vuelve a llenar, el GC vuelve a eliminar lo que no es necesario y los objetos que sobreviven pasan a formar parte de la generacin 1, quedando nuevamente la generacin 0 vaca.

Llegar un momento en el cual la generacin 1 tambin se llene y el GC tendr que actuar nuevamente. Es interesante fijarse en que, puesto que esta generacin tiene un espacio ms grande, tardar ms en llenarse y el GC tardar ms en actuar aqu. Adems, si suponemos la premisa 1 como cierta entonces liberaremos aquellos objetos nuevos transitorios rpidamente y eficientemente recorriendo secciones bien pequeas.

Como decamos, llegar un momento en que la generacin 1 se llene. En ese momento el GC recorrer la generacin 1 y todos aquellos objetos que sobrevivan a dicha recoleccin pasarn a la generacin 2, que ser an ms grande. Y vuelta a empezar. Normalmente las implementaciones de montculo de .NET soportan hasta 3 generaciones: 0, 1 y 2, con lo cual aqu hemos terminado.

Como vemos, se cumplen las tres premisas, porque: Los nuevos objetos se almacenan en la generacin 0, que es la ms pequea.

Los objetos ms viejos se almacenan en generaciones ms grandes, que tardan ms en llenarse y por tanto en limpiarse. Casi nunca se recorre todo el montculo, sino solamente sectores (generaciones). Solamente se recorre todo el montculo cuando se intenta instanciar en memoria un nuevo objeto y tanto la generacin 0 como la generacin 1 y la 2 estn llenas, lo cual es realmente muy poco frecuente. Manipulacin del GC

Cuando dije que la liberacin de recursos en .NET es automtica no fui totalmente sincero. Es posible forzar una pasada del GC, pero esto solamente es conveniente realizarlo cuando sabemos a ciencia cierta que un conjunto de recursos costosos que se han estado utilizando no se van a utilizar ms y por tanto deberan ser liberados. El mtodo a utilizar para forzar al GC a liberar recursos es System.GC.Collect().

La clase System.GC es la que implementa el recolector de basura. Algunos mtodos tiles son:

- GC.MaxGeneration(), que devuelve el numeral de la mxima generacin en el GC soportada por el montculo. - GC.WaitForPendingFinalizers(): Este mtodo suspende el hilo hasta que se procesen los finalizadores pendientes. - GC.GetGeneration(): Que permite manipular las distintas generaciones del GC. Algunas sencillas prcticas que permiten sacar partido del GC

A pesar de que la gestin de la memoria es un proceso bastante automtico en .NET, es muy conveniente entender cmo funciona entre bastidores porque s que hay ciertas prcticas que permiten sacar provecho de su funcionamiento. Por ejemplo: Limitar el uso de objetos estticos, ya que constituyen races que no se pueden recolectar. Utilizar la localidad de referencia en la medida de lo posible, es decir: declarar los objetos lo ms cerca posible de su primer uso. Esto evitar la creacin de objetos mucho antes de su uso. Tratar de concentrar las operaciones sobre un objeto en una porcin pequea de cdigo, pues el GC se basa en la premisa de que el uso de un objeto est concentrado en unas pocas lneas de cdigo y despus no se vuelve a utilizar (nuevamente, localidad de referencia).

Usar convenientemente Close() y Dispose() para liberar recursos como conexiones, buffers, streams, etc. que ya no se van a utilizar, invocndolos lo antes posible, una vez que los objetos no van a ser utilizados ms. A la hora de implementar un destructor, primero: plantese si realmente es necesario hacerlo y segundo: no codifique pensando en el orden que van a seguir las operaciones a la hora de liberar recursos, recuerde: la funcin del GC es no determinstica. Para los que vengan de un lenguaje C++, recuerden que el uso de destructores es mucho menos frecuente en .NET que en C++, sencillamente porque el manejo de la memoria es muy diferente. Bsicamente es automtico.

Esto es todo. Espero que esta serie de dos artculos les haya parecido til o al menos interesante a muchos programadores de .NET.

También podría gustarte