Quienes hemos desarrollado aplicaciones o hemos estado a cargo de la mantención de un sitio web, en alguna oportunidad nos topamos o seguramente lo haremos en el futuro, con una excepción del tipo "Out Of Memory Exception" o "OOM Exception".
¿Por qué ocurren los Out Of Memory Exceptions?
Quien haya visto esto, se preguntará, ¿Cómo es posible que tenga 4 GB de memoria en el servidor y mi aplicación se cae por falta memoria cuando "sólo ha ocupado" 800 MB?
Ahora veremos someramente cómo funciona la administración de memoria en Windows y en .NET. No es mi intención cubrir el tema de forma extensa, en especial la administración de .net. Eso lo veremos en sucesivos posts.
Antes de comenzar, quiero hacer hincapié en que este post está basado en otro post de este blog
, del cual incluso tomé "prestada" la imagen.
Utilizaremos una analogía para explicar el funcionamiento ya que será mucho más fácil de visualizar y entender. En este caso, la analogía corresponde a un restaurant donde las personas usualmente se juntan a comer en grupos.
¿Cómo se vería este restaurant antes de estar abierto al público, pero una vez que se haya instalado todo lo necesario para su funcionamiento?
Este es un estado "similar" al que correspondería al estado antes de empezar a ejecutar la aplicación. Por "similar" me refiero a que no consideraremos la memoria utilizada para cargar las dlls, utilizada por threads y otros elementos vitales en el funcionamiento de ésta. Sólo nos enfocaremos en la memoria que es pedida y liberada durante el funcionamiento
Este restaurant tendría todas sus mesas disponibles para los clientes que van entrando a comer.
¿Qué sucede cuando se pide memoria al sistema operativo?
Debido a que la solicitud de memoria es un proceso costoso, y suponiendo que se va a realizar miles o millones de veces durante la ejecución de la aplicación, el funcionamiento desde el punto de vista del sistema operativo es diferente a como usualmente lo podríamos suponer.
La memoria, desde el punto de vista del sistema operativo, tiene 3 estados posibles: libre (FREE), reservada (RESERVED) y comprometida (COMMITED).
Una aplicación, al necesitar memoria, le solicita al sistema operativo que le reserve (FREE->RESERVED) un espacio (a veces pequeños, otras veces de varios MBs), para posteriormente manejar élla las miles de solicitudes (RESERVED->COMMITED) y liberaciones (COMMITED->RESERVED) que ocurren a cada segundo. Es decir, el sistema operativo le entrega un pedazo de memoria y la aplicación lo administra. Cuando no le queda espacio libre, le pide al sistema operativo que le reserve otro espacio y el proceso continúa.
La memoria reservada es lo que se conoce con el nombre de VIRTUAL BYTES, y la memoria comprometida, PRIVATE BYTES. Hasta Windows 2003 Server (incluido), ninguno de los contadores de memoria del Task Manager representa estos valores. Solo es posible obtenerlos con performance monitor. Windows Vista si los incorpora, y seguramente Longhorn lo hará también.
Bueno, y ¿qué sucede con el restaurant?
La aplicación es el restaurant, y a medida que llegan clientes, se va ocupando el espacio en él. Lo importante en este punto es saber cómo se llena.
Supongamos que llamamos al restaurant y decimos que vamos 3 personas. Rara vez, un restaurant tiene mesa para 3 personas. Usualmente son de números pares. Si existe una mesa para 4 personas, nos será reservada para que cuando lleguemos, la podamos utilizar. Lamentablemente para el restaurant, estará perdiendo dinero ya que no podrá utilizar ese espacio libre.
Después de unas horas de funcionamiento, nuestro restaurant se vería así. Todo el espacio blanco es la memoria disponible. Las sillas azules corresponden a las que están reservadas y las rojas a las comprometidas.

En la primera mesa, alguien vino a comer sólo pero tuvo que ocupar una mesa de a dos. Mal para el restaurant.
En la segunda mesa, no le fue tan mal al restaurant. Tuvo que asignar una mesa de a 6 a la que asistieron 4.
La tercera mesa, fue utilizada por 3 personas, hasta llegar al peor escenario para el restaurant. Llega 1 sola persona y le tiene que entregar una mesa de 4.
Por favor notar que la solicitud de las mesas no es bajo ningún criterio de forma secuencial como lo estoy mencionando hasta ahora. La llegada de personas (solicitud de memoria) ocurre en cualquier orden.
¿Dónde comienzan los problemas?
¿Qué sucede si llama una persona y pide una reservación para 4 personas? No existe ninguna mesa disponible para sentar a las cuatro personas.
Alguien podría decir que se puede sentar 1 persona en la primera mesa, 2 en la segunda y 1 en la tercera, pero seguramente los comensales querrán sentarse juntos, y con mucha razón!!. De la misma forma, las solicitudes de memoria se deben entregar en un bloque contiguo.
Otro escenario que no se da en la imagen, corresponde al hecho de suponer que por que en una mesa hay 3 espacios disponibles y en la mesa de al lado 1, se juntan las dos mesas y se les sienta a las 4 personas juntas. Lamentablemente, por cómo se maneja la memoria, no es posible reacomodar las mesas (espacios reservados) una vez que han sido asignadas, como tampoco mover las mesas del restaurant!!!
¿Qué sucede entonces? Suponiendo que no existe más espacio que reservar en el restaurant, al cliente se le dice que está lleno, lo que corresponde a un Out Of Memory.
El dueño del restauran puede estar muy triste por que dejó ir a cuatro clientes cuando potencialmente había espacio para 9 personas más.
El administrador de la aplicación puede estar muy molesto porque la aplicación arroja errores de Out Of memory, pero aún hay memoria en el servidor, o eso dice task manager (en otro post veremos qué es lo que realmente entregan los valores del task manager).
¿Cómo saber cuánta memoria se desperdica? La respuesta es VIRTUAL BYTES - PRIVATE BYTES, es decir, lo que he reservado menos lo que he ocupado.
¿Y desde el punto de vista de .net y el garbage collector (GC)?
El GC le solicita al sistema operativo espacios de 64 MB de memoria contigua, lo que correspondería a una gran mesa de muchos asientos. En este restaurant especial, las personas no se molestan por sentarse juntas en la misma mesa (curioso!!).
Cada vez que se crea un objeto, la memoria requerida por éste se obtiene desde después de la última persona sentada, y así sucesivamente hasta que se llena la mesa.
De vez en cuando, alguien (GC) revisa que las personas que han terminado de comer, efectivamente desalojen el lugar (se libera la memoria) y todos los otros comensales se desplazan para dejar más espacio al final de ésta. Otros argumentan que están esperando a alguien (strong reference) así que aunque ya terminaron, no se levantaran de la mesa (pero si se desplazan), y por último, algunos personajes de "mal temperamento" dirán, no me muevo de aquí (ni se desplazan) porque tengo asiento con ventana y la vista está muy hermosa hoy. Estos últimos son objetos que han sido referenciados desde fuera del código manejado (pinned object).
Para el último caso, los pinned objects, nadie puede moverse hacia delante de la mesa mientras ellos estén en ella. Esto produce espacios vacios en la mesa, entre ellos u los de adelante, lo que se conoce como fragmentación de memoria en .Net. Este tipo de objetos no se puede mover ya que en código no manejado, las posiciones en memoria jamás cambian, no asi en .net ya que cada desplazamiento de la mesa implica nuevas posiciones para los objetos.
Una vez que se llena la mesa, se le pide al sistema operativo otra mesa de 64 personas, ¿y si no hay disponible?..ya adivinaron no?, Out Of Memory Exception.
Debido a lo anterior, tenemos que ser muy cuidadosos al desarrollar código, haciendo dispose y liberando los recursos apenas dejen de utilizarse. Ya hablaremos de esto pronto.
Patrick.