Fugando memoria con .Net
Una de la grandes maravillas de los lenguajes manejados es que es imposible fugar memoria... al menos en teoria. Pero la verdad es que aunque es mucho más dificil fugar memoria, no es imposible. Es imposible fugar memoria en el sentido clásico del termino, en el sentido de reservar memoria, con un new, un malloc, un SysAllocString, etc y no recordar liberarla con el correspondiente delete, free, o SysFreeString, algo muy común en C o C++, esta es la buena noticia y es más por si sola justifica el uso de lenguajes manejados. Buscar fugas de memoria puede ser muy divertido, pero, desde luego no es lo más productivo que podemos hacer como desarrolladores.
La mala noticia es que nada nos libra de otro tipo de fugas de memoria, igualmente perniciosas pero, cierto es, mucho menos frecuentes, por citar algunos ejemplos: podemos añadir objetos a una colección que actue de cache y nunca sacar objetos de manera que la colección crezca indefinidamente (esta fuga suele ser facil de cazar...) o algunas un poco más sutiles como el ejemplo que hoy propongo. Otra forma habitual de fugar memoria en .Net es cuando se utilizan recusos nativos mediante COM Interop o P/Invoke.
Hacía bastantes años, desde que deje Panda Software y la programación en C/C++ y COM que no me enfrentaba a un caso serio de perdida de memoria. A pesar de haber escrito mucho código en .Net. Pero desde hace algún tiempo un proyecto en el que estoy trabajando presentaba una seria perdida de memoria en un servicio que se ejecuta de manera continua antendiendo peticiones del resto de componentes de la aplicación. El resultado es que al cabo de una semana y tras agonizar durante un día en condiciones de baja memoria, en un servidor de producción con 4Gb de memoria, la aplicación cascaba con una agónica OutOfMemoryException. Las entradas en el registro de eventos eran bastante elocuente (esta es un ejemplo de una de ellas, pero variaban según el componente que tratase de hacer la asignación de memoria 'definitiva'):
EventType clr20r3, P1 AppQueFuga.exe, P2 1.0.0.137, P3 45d6968e, P4 mscorlib, P5 2.0.0.0, P6 4333ab80, P7 116e, P8 29, P9 system.outofmemoryexception, P10 NIL
Buscar fugas de memoria siempre me ha apasionado, es un tipo de bug realmente interesante, y la verdad me ha traido grandes recuerdos...
El reto que os planteo hoy es ¿alguien sabe por qué el siguiente código, caso mínimo extraido desde le problema real y a simple vista totalmente inofensivo, fuga memoria?. Para ver que fuga memoria solo tenéis que compilar el código (lo tenéis como adjunto a este post), ponerlo a ejecutar y ver el consumo de memoria del proceso. Fijaros que solo uso clases manejadas, lo que hace aun más interesante esta fuga de memoria:
using System;
using System.Diagnostics;
namespace Leaker
{
static class Leaker
{
public static TraceSwitch CurrentTraceSwitch
{
get { return new TraceSwitch("Test", "Test", "Test"); }
}
}
class Program
{
static void Main(string[] args)
{
//Bucle infinito
while (true)
{
//Cada vez que se ejecuta esta línea perdemos memoria
Console.WriteLine(Leaker.CurrentTraceSwitch.Level);
}
}
}
}
En proximos post voy a comentar cómo diagnostique esta fuga de memoria y daré la solución a por qué se produce, estad atentos, os aseguro que será interesante.