Liberación de memoria en código manejado (¿Dispose, Finalize, Object = Nothing, GC.Collect?)

Para quienes venimos del desarrollo utilizando Visual Basic 6.0, una de las primeras cosas que nos enseñan al empezar a utilizar código manejado (framework), es que ya no es necesario liberar la memoria porque “.net lo hace por ti”. Esta última parte entre comillas, además de ser incorrecta en su definición, es muy engañosa/confusa para quién es nuevo utilizando el framework.

¿Por qué está incorrecta en su definición?

De partida, el decir que .net lo hace es tan amplio que pierde el enfoque y no queda claro quiénes son los actores involucrados.


El actor principal es el garbage collector (GC). Éste está encargado de reservar la memoria desde el sistema operativo (grandes pedazos de memoria) y administrar los requerimientos de memoria de nuestra aplicación (pequeños pedazos de memoria). Esta administración comprende las tareas de asignar la memoria que es requerida por nuestra aplicación, por ejemplo las variables, para posteriormente reclamarla una vez que se ha dejado de utilizar. Para más información, ver el siguiente post sobre el manejo de memoria.

¿Por qué es engañosa/confusa para desarrolladores novatos en código manejado?

Si el problema se mira desde 10.000 metros de altura y si todos los componentes que se usan están correctamente programados, podemos decir que el GC si libera la memoria por ti.


Veamos las sutilezas que hacen que lo anterior pueda no cumplirse.


El GC es no-determinístico. ¿Qué significa esto? Significa que éste limpia la memoria cuando ÉL estima que es necesario y no cuanto TÚ quieres que lo haga; su ejecución no está determinada por ti, ni se ejecuta siguiendo un patrón detectable desde código. Sí lo hace siguiendo un algoritmo de optimización/adiestramiento interno, pero no es fácilmente detectable por uno como desarrollador. Además, al ser un algoritmo que se va adiestrando con el tiempo, su frecuencia de ejecución no es siempre repetible o predecible.


Entonces, si nos vamos a 10.000 metros de altura y para un tiempo T >> 0, podemos decir que la memoria será reclamada (liberada) por el GC, un poco más tarde de lo que se haría en VB 6.0, pero será reclamada.


Por otro lado, el GC no libera la memoria conocida como no manejada, es decir, la memora que él no administró. ¿Quién libera esta memoria? La respuesta tiene matices, primero el desarrollador, pero si éste no lo hace, alguien debe hacerlo.


Cuando se programan componentes que manejan recursos no manejados, el programador DEBE implementar el [patronDispose], que incluye la interfaz [IDisposable]. Esto debe hacerlo sí o sí.


Programadores expertos podrán argumentar que no es necesario implementar el [patronDispose] o que se puede implementar a medias (ver sección de los problemas más abajo referente al mito del finalizador). Esto es cierto, pero dependerá del control que ellos tengan sobre el uso de los tipos (clases) generados por ellos. Desde el momento en que ellos no controlen quién usará sus tipos, será entonces obligación hacerlo ya que es un estándar esperable.


Yo, como desarrollador, debiera ser el responsable de liberar la memoria no manejada llamando al método Dispose una vez que he dejado de utilizar el objeto de ese tipo. Esto, a diferencia del funcionamiento del GC, liberará inmediatamente la memoria no manejada utilizada por el tipo.

¿Qué sucede si el desarrollador no llama a Dispose?

Entonces dependerá de varios factores el que afecte o no a mi aplicación. Veamos algunos de ellos.


Si quién desarrolló el tipo que estoy utilizando implementó el [patronDispose] de forma correcta, los recursos manejados serán liberados cuando se ejecute el finalizador. Éste, de la misma forma que el GC, es no-determinístico.


Al no ser determinístico, la liberación de los recursos no manejados podrá hacerse tan tarde que podríamos encontrarnos con [OOM] o quedarnos sin conexiones a SQL Server (esto yo lo he visto en algunos casos donde he trabajado).


Tristemente, si el código no implementó Finalize porque el desarrollador novato no supo que había que hacerlo o porque el experto confió en que quién lo iba a usar llamaría a Dispose, definitivamente nos encontraremos con [OOM].

¿Dónde empiezan los problemas?

Uno de ellos se da porque rara vez un programador implementa el finalizador. Esto se debe a la existencia de un mito/costo asociado a éste. Es totalmente cierto que tener un tipo que implementa el finalizador tiene un sobrecosto en rendimiento comparado a un tipo que no lo implementa. Ahora, es muy cierto también que si se implementa el [patronDispose] de forma correcta, existe una línea de código en la función Dispose que quita parte de ese sobrecosto. Veamos el código de ésta:






public void Dispose()


{


    Dispose(true);

    GC.SuppressFinalize(this);

}

 

Sin entrar en demasiados detalles, el sobrecosto mencionado de tener un tipo que implementa Finalize se debe a que cada vez que se crea una instancia de éste tipo, este nuevo objeto se agrega a una cola especial de procesamiento. Bueno, esta función SuppressFinalize saca la instancia de la cola. Entonces, al final, el único sobrecosto está en agregar y sacar una instancia de la cola. No se cuantificar este costo, pero puedo asegurar que es mucho menor a los problemas producto de memoria no liberada. Se debe entender que una vez que se ha hecho Dispose de los recursos no manejados, ya no hay necesidad de llamar a Finalize porque no hay nada que finalizar.


Por eso es importante que el desarrollador llame al método Dispose, ya que además de garantizar la correcta liberación de recursos no manejados, también se produce esta optimización. Ese es otro de los problemas. El desarrollador no sabe que tiene que llamar a Dispose porque le dijeron que .net libera la memoria automáticamente.


El último de los problemas se da por que el desarrollador escucha o lee [recmalas] o poco precisas y decide usar el mismo código que estaba tan bien justificado (es en sentido irónico) en este otro sitio. Esto se refiere al uso de GC.Collect.

¿Y si llamo a GC.Collect?

Llamar a GC.Collect no tiene ningún efecto positivo; más aún, los efectos negativos producto de la ejecución de ésta podrían impactar severamente el rendimiento y consumo de recursos. Esto tiene que quedar claro ¡ya!


Jamás debe llamarse a GC.Collect. Existen excepciones contadas con menos dedos que los que tiene una mano, en las cuales el hacerlo podría (potencialmente) tener beneficios, pero esas no están dentro de las labores de desarrollo tradicional. Para el trabajo día a día, GC.Collect no está dentro de las funciones a utilizar.

Si no puedo recolectar la memoria, igualo los objetos a nothing/null

El igualar los objetos a nulo tiene, en la práctica, un impacto mínimo en la liberación de memoria. MSDN define esto como:






In Visual Basic .NET, setting an object to Nothing marks the object for garbage collection, but the object is not immediately destroyed.

 

Es correcto. Si se iguala un objeto a nulo, lo único que se está logrando es marcándolo para que sea liberado, la próxima vez que se ejecute el GC, algo que ya sabemos que ocurre de forma no determinística y en una frecuencia no conocida por los desarrolladores. Entonces, ¿qué gano haciéndolo?


Existen, al igual que el llamado a GC.Collect, contadas ocasiones donde asignar nulo a variables puede ayudar, pero todo dependerá de las probabilidades.


Supongamos que estamos en una función donde se han creado objetos que consumen mucha memoria (datasets, colecciones, hashtables, etc.), y que en alguna de las líneas que vienen más abajo, se hará una llamada a una función que demorará bastante (Web Service, SQL Server, etc.). Si llegase a ocurrir una recolección de memoria mientras se ejecuta esta función larga, todos estos objetos grandes envejecerán (pasarán de generación en el GC) y no serán liberados ya que aún se están “usando”.


Ahí es donde se puede hacer la diferencia. Si yo sé que no se usarán más adelante en la función, entonces puedo igualarlos a nulo y en la eventualidad que se produzca una recolección, estos serán efectivamente recolectados por el GC (no envejecerán) y la memoria será liberada.


¿Qué otras opciones existen donde valga la pena hacerlo?


Yo al menos no conozco ninguna otra, lo que por cierto no significa que no haya.

¿Y si igualo a nulo y llamo a GC.Collect?

Nuevamente, no se debe llamar a GC.Collect salvo contadísimas excepciones, las que no hemos discutido aquí. Si tú has vivido una situación donde hayas podido demostrar fehacientemente que el llamado a GC.Collect produjo un resultado positivo, te ruego compartirla.


Ahora, si tienes otro punto de vista o situación vivida que difiera de lo que acabamos de ver, también te ruego compartirla.


Patrick.

Return to Childhood 2005, otro momento imperdible

Hablando de momentos inolvidables, aunque hace ya un par de años que ocurrió, definitivamente fue y será un momento imperdible. Fish en Chile tocando el impresionante álbum Misplaced Childhood completo.


No soy crítico de música, pero los “especializados” dijeron que tenía poca voz y que la banda no estaba afiatada. A callar mejor y tengan respeto.



Roger Waters, impresionante

Ya sé que no tiene nada que ver con tecnología, pero igual. Tener la oportunidad de ver a Roger Waters en Chile, se da cada mil años (o cada 5, desde la última vez).



Charla MSDN Chile, Mejorando el rendimiento de tus aplicaciones

Este jueves 22 de marzo, presentaremos junto a Luis Hereira la primera de una serie de charlas donde veremos los principales problemas de rendimiento de las aplicaciones.


Nuestro interés es ver en cada charla 2 o 3 temas en profundidad, demostrando con pruebas y ejemplos los puntos expuestos. Adicionalmente se presentarán metodologías o tips para detectar y solucionar el problema presentado.


En esta oportunidad, veremos los siguientes temas:
1.- Concatenación de strings
2.- Uso descuidado de variables de sesión
3.- Uso (y abuso) del Garbage Collector (aún no sabemos si irá en esta charla)


Más información disponible en [cafeina].


El link para la inscripción al evento es: http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032332352&Culture=es-CL

Dispose en SPWeb, SPSite y SPListItemCollection, desarrollando Web Parts para SharePoint

Después de una extenuante semana de viaje viendo un caso fuera de Chile, el cual me obligó a estar offline casi todos los días, me doy un tiempo para escribir y dar a conocer los usuales problemas con que uno se enfrenta cuando analiza web parts que corren sobre SharePoint*.


Hasta hoy, he visto web parts desarrolladas que normalmente tienen pérdidas de memoria (memory leaks), principalmente de memoria no manejada pero también de memoria manejada.


Como consecuencia de lo anterior, el continuo uso de éstas (Web Parts mal codificadas), llevará irremediablemente a encontrarnos con [OOM] en nuestra aplicación. Una forma de sobrevivir ante este problema de pérdidas de memoria es configurar el pool de aplicación de IIS 6.0 o 7.0 para que recicle cuando la memoria consumida (privada o virtual) llegue a una cantidad de megabytes determinada. Para la versión 5.0, existe una aplicación llamada IIS Recycle que hace algo similar, aunque asp.net en IIS 5 tienes capacidades de reciclamiento.


El principal problema con el desarrollo de Web Parts para SharePoint es que, al menos hasta la versión 2003, SharePoint Portal depende mucho de componentes COM que utilizan memoria no manejada, y el desarrollador eso no lo sabe, no se da cuenta o definitivamente olvida liberar los recursos no manejados (que incluye llamar a Dispose cuando corresponda).


Si te interesa y quieres confirmar esto, dale una mirada con reflector al ensamblado Microsoft.SharePoint.Library.dll.


Adicionalmente al problema de la no liberación de recursos no manejados, algunas bibliotecas de SharePoint están programadas de tal forma de que si necesita un recurso y este no se ha instanciado (o se ha destruido adecuadamente), ésta lo vuelve a generar. Esto complica más el panorama ya que si un desarrollador fue cuidadoso y lo liberó, el posterior uso de otra función puede revivir el objeto. Esto no es cierto para todas las funciones, pero algunas tienen comportamiento peculiar. Veámoslo en acción (toda la acción que se puede tener en un blog [;)]).


Supongamos que un desarrollador obtiene una SPListItemCollection en un objeto oList y para liberar los recursos no manejados, llama responsablemente a oList.List.ParentWeb.Dispose(), pero si después decide obtener la cantidad de objetos de esta colección llamando a Count, se podrá vivir la situación detallada anteriormente. El llamado a Count revivirá el objeto SPWeb.


El método Dispose  de SPWeb llama a Close, que llama a el método a sobre at, que es de tipo Microsoft SharePoint.Library.a. Antes de seguir, ten en cuenta que ahora se enreda bastante más.

 




public void Dispose()


{


    this.Close();


}

 

public void Close()


{


    if (this.at != null)


    {


        this.at.a();


        this.at = null;


    }


}

 

Y luego, el llamado a Count de SPListItemCollection:






public override int get_Count()


{


    this.a(true);


    return this.c;


}

 

El código del método a (que está ofuscado) es:






private void a(bool A_0)


{


    string text;


    SPWeb web;

    a a; //Esto se ve mal, pero es producto de la ofuscación

    string viewXml;


    if (this.f)


    {

        web = this.b.Lists.Web;
       
a = web.l();
        <Cortado para abreviar…>t;

}

 

La variable web referencia ahora al valor de this.b.Lists.Web y luego se llama al método l (ele) (ofuscado también). Recordemos entonces que web referencia a un objeto SPWeb (Web) que está referenciado por un objeto SPListCollection (Lists) que no debe ser confundido con el objeto del cual llamamos a Count del tipo SPListItemCollection. Este último (SPListCollection) a su vez está referenciado por un objeto de tipo SPList (b), el cual es parte de nuestro objeto inicial, SPListItemCollection (¿no te dije que se enredaba?).


El método l (ele) es internal y el código que nos muestra reflector es el siguiente:






internal a l(){


    this.i();


    return this.at;


}

 

Como ven, está retornando la variable at. Esta misma variable era la que se había asignado a null cuando se ejecutó SPListItemCollection.List.ParentWeb.Dispose(). Conviene mostrar el código de ParentWeb disponible en SPList para aclarar toda la relación.


ParentWeb retorna el objeto SPWeb asociado a la variable m_Lists (del tipo SPListCollection).






public SPWeb ParentWeb


{


    get


    {


        return this.m_Lists.Web;


    }


}

 

Solo para demostrar lo anterior, el código de i y h se despliegan ahora. Verán que el objeto at es nuevamente generado si este es null.






private void i()


{


    if (this.at == null)


    {


        this.h();


    }


}


 


private void h()


{


    int num = this.b.i();


    bool flag = -1 == num;


    bool flag2 = null != this.b.g();

    this.at = g.a(!flag2, this.al, this.b.d(), ref num);  

    <Cortado para abreviar…>


}

 

Si te has mareado con tanto objeto, es entendible. Lo importante aquí es encargarse de liberar toda la memoria referenciada por los objetos de SharePoint.


Si estás desarrollado Web Parts, no puedes no leer el siguiente documento. Considéralo como lectura obligatoria para desarrollar web parts que vivan sin problemas. La dirección del documento en MSDN  es http://msdn2.microsoft.com/en-us/library/ms778813.aspx y éste detalla de forma casi perfecta como liberar toda la memoria no manejada cuando programas Web Parts.


¿Por qué no digo que está perfecto? Porque le faltó agregar parte del caso que vimos hoy. En ninguna parte del documento indica la liberación del objeto SPWeb que esta referenciado por SPListCollection y a su vez por SPList y finalmente por SPListItemCollection. Es cierto que podría subentenderse, pero no está explícito.


Eso sí, tiene gran detalle para mostrar con ejemplos, código que está mal escrito y como debe escribirse correctamente. Si pudiésemos contar siempre con ejemplos así de MSDN, sería fantástico. Incluso, da a conocer sutilezas de la implementación que pueden hacer que tu aplicación colapse si no sigues las recomendaciones. Como ejemplo, vean esta aclaración que aunque está en inglés, no es difícil de entender.

SPSiteCollection [ ] Index OperatorThe SPSiteCollection [] index operator returns a new SPSite object for each access. A SPSite instance is created even if that object has already been accessed.

Si bien hoy no hablamos de SPSite, la limpieza de éste es tan importante como la de SPWeb.

 

*Realmente no sé si las web parts corren sobre otra aplicación que no sea SharePoint [;)], ya sea en la versión para Windows 2003 conocida como Windows SharePoint Services, como la versión full llamada SharePoint Portal server (http://www.microsoft.com/latam/office/sharepoint/prodinfo/relationship.mspx).


Saludos,
Patrick

Posts y Tips de baja calidad, y el impacto de éstos

En esta oportunidad, traigo a colación (dicho típico chileno) un “TIP” que apareció en un sitio relacionado con tecnología, y que me entero por un mail que me llega a la cuenta de la empresa donde trabajo.


No voy a reclamar contra el hecho de que me llegue un mail que no he solicitado a la casilla de mi empresa, sin necesidad de haberme inscrito ni visitado el sitio. No señor, no reclamaré (¿o ya lo hice?).


Lo que si me produce preocupación y algo de molestia es cuando las personas postean material de pobre contenido que afecta al resto.  Me gustaría haber utilizado un calificativo más rudo en vez de material pobre, pero me abstengo con dificultad.


Aclaro que no estoy en contra de escribir, ni tampoco apoyo que sólo escriban personas que son técnicamente muy capacitadas, y con contenidos de alto nivel. Creo que toda persona tiene el derecho de escribir lo que quiera, pero en caso de escribir contenido técnico para compartir, al menos espero que él, como escritor, entienda lo que está exponiendo y las consecuencias del uso de éste.


En el post que hago referencia ahora, es triste ver que alguien que no tiene conocimiento de lo que escribe, lo justifica, ya que  según él y quienes lo apoyan, lo hacen sosteniendo que  .net tiene un mal manejo de la memoria y tiende a utilizar más de la que realmente necesita. La dirección es http://msd2d.com/Content/Tip_viewitem_03NoAuth.aspx?id=498a515b-3977-4c3b-b387-8cd548707415&section=dotNet


Más inquietante aún, es el hecho de que muchas otras personas lo copiarán sin pensar, ni entender*, que es lo que están copiando, ni en el impacto que esto tendrá en su aplicación. A eso me refiero con postear material pobre que afecta al resto.


He hecho una marca * en entender ya que muchas veces no es necesario ni posible entender cómo funciona algo. Como ejemplo, para un desarrollador cualquiera, no veo utilidad en realizar un estudio de cómo funciona un algoritmo de encriptación, como Rijndael. Lo importante eso sí, es conocer cómo se implementa adecuadamente. Se debe estudiar.


Para el caso de este post, son 3 líneas de código con instrucciones. No es difícil utilizar un buscador en internet como Live, Google o el de tu preferencia y buscar la definición de las funciones utilizadas y tratar de entender que hacen, y si efectivamente hacen lo que los escritores del post dicen.

 

Patrick.