sábado, 12 de abril de 2008

Pérdidas de memoria en .NET

Una de las mejores cosas de las plataformas "manejadas" como Java y .NET es la posibilidad de no tener que preocuparnos con los detalles del control del uso de la memoria, sin embargo hay situaciones que pueden provocar pérdidas de memoria si no las tenemos en cuenta como intenta explicar Calvin Hsia en Examine .Net Memory Leaks .

Este es un tema que muchas veces confunde, asi que me tomé la libertad de copiar su ejemplo y llevarlo a C#.


using System;

namespace Test
{
class Program
{
static void Main(string[] args)
{
var oldPeak = 0L;
for (int i = 0; i < 100; i++)
{
var oWatcher = new MyWatcher();
// oWatcher.UnSubscribe(); // Quitar comentario para quitar el controlador
oWatcher = null;
GC.Collect(); // Recolectar
GC.WaitForPendingFinalizers(); // Esperar por los finalizadores ( destructures )
GC.Collect(); // Recolectar los que tienen destructores
var newPeack = System.Diagnostics.Process.GetCurrentProcess().PeakWorkingSet64;
if (i > 0)
Console.WriteLine("Todo liberado? {0} WorkingSet ={1} Pico ={2} Delta ={3}",
i, System.Diagnostics.Process.GetCurrentProcess().WorkingSet64,
newPeack, (newPeack - oldPeak));
oldPeak = newPeack;
}
GC.Collect(); // Recolectar
GC.WaitForPendingFinalizers(); // Esperar por los finalizadores ( destructures )
GC.Collect(); // Recolectar los que tienen destructores
Console.ReadKey();
}
}

class MyWatcher
{
string[] _myLargeMemoryEater = new string[100000];
System.IO.FileSystemWatcher _fsw;
public MyWatcher()
{
_fsw = new System.IO.FileSystemWatcher();
_fsw.Path = "c:\\";
_fsw.Filter = "*.*";
_fsw.Created += _fsw_Created;
_fsw.EnableRaisingEvents = true;
}
public void UnSubscribe()
{
_fsw.Created -= _fsw_Created;
_fsw.Dispose();
}

void _fsw_Created(object sender, System.IO.FileSystemEventArgs e)
{
Console.WriteLine(e.FullPath);
}
~MyWatcher()
{
Console.WriteLine("Terminado en hilo "+System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
}
}
}


Al ejecutar el programa vemos como va consumiendo mas y mas memoria aun cuado en cada iteración estamos forzando la recolección de basura y la instancia que estamos creando no tiene aparentemente ninguna referencia. Aparentemente.

En realidad la referencia si existe porque al "enganchar" un evento a la clase FileSystemWatcher es como si estuviéramos enganchando un evento a un objeto del sistema operativo y mientras tal exista la referencia continuará ahi, no importa que no tengamos en el programa una referencia directa aparente al objeto.

Ahora si quitamos el comentario a la linea que dice
// oWatcher.UnSubscribe();

cada llamada al recolector de basura libera la memoria debido a que la llamada a UnSubscribe "desconecta" nuestro objeto del FileSystemWatcher, sin embargo es bueno notar que dos cosas se hacen aqui: una desconectar el evento, la otra liberar el FileSystemWatcher, por que ? El FileSystemWatcher como había dicho representa un objeto del sistema operativo, de hecho en su implementación interna debe tener un handle a un objeto del sistema operativo y no va a ser liberado hasta que se llame a Dispose, podemos hacer la prueba comentando esa linea y ver los resultados.

Como podemos ver aunque .NET maneja la memoria por nosotros, somos responsables cuando desde nuestro programa colocamos referencias a elementos externos, como en este caso objetos del sistema operativo.

miércoles, 9 de abril de 2008

Donde está Oracle Home ?

El Oracle Home es, entre otras cosas, la forma de localizar los productos de Oracle en una máquina. Pueden haber varias pero siempre hay una por defecto que es la utilizada por la mayoría de las aplicaciones que dependen de un cliente de oracle para funcionar.

En un mundo ideal el cliente de Oracle es instalado por un administrador y las aplicaciones solo tienen que usar el que esté ahi y no preocuparse mucho mas. En el mundo real el cliente es instaldo muchas veces por cualquiera y en muchas ocaciones es necesario saber donde está. Si como parte de la configuración de tu aplicación necesitas decir a que instancia/servicio de Oracle te conectas es posible que necesites saber cuales son las que están disponibles en la máquina y como esa información está en el archivo tnsnames.ora , tienes que saber en que directorio está instalado oracle home por defecto.

Siendo una cosa tan importante es curioso que halla tanta confusión sobre esto especialmente en plataforma Windows. Eso se debe en parte a que la documentación de Oracle al respecto no es muy abundante.

Lo que yo he implementado y hasta el momento me ha funcionado bien ( creo que lo vi en alguna parte en la documentación de Oracle ) es que hay un archivo llamado oracle.key en el directorio bin de la instalación que dice en que lugar en el registro de Windows está la información del Oracle Home. Por lo tanto para encontrar el ORACLE_HOME:

1 .- Recorrer los directorios que están en el camino ( variable de ambiente PATH, separados por punto y coma ).
2 .- Detenerse en el primero donde se encuentre el archivo oracle.key.
3 .- En ese archivo está el lugar en el registro donde está la información del ORACLE_HOME ( por ejemplo SOFTWARE\ORACLE\KEY_OraDb10g_home1 ).

Y ya tenemos la ubicación del Oracle Home en el registro, de ahi ganamos otros datos interesantes como directorios , valores de configuración , ubicación de otros productos, etc.