Un poco de windbg aplicado...

Ayer Rodrigo planteó un problema curioso en su blog, dios que rabia me dio no tener las herramientas instaladas en el portatil que tenía en casa!!!  sólo tenía ie y una tarjeta 3G para navegar por la red, a si que ni si quiera pude bajármelas :_)

Pero esta mañana me he puesto con ello en cuanto he llegado a la oficina :P

Ahí va la aproximación que yo he seguido para intentar atajar el problema.

(Nota: Por si alguien lo intenta resolver... tened en cuenta que las direcciones de memoria vana  variar siempre y tenéis que buscar las que os apliquen a vosotros)

Empezando con el problema

Como es Rodrigo quien dice que la aplicación pierde memoria, pues me lo creo y listo, así que asumo que una vez la ejecute va a empezar a crecer la memoria. Hay un montón de herramientas para ver cómo crece la memoria, que tipo de memoria es la que crece, etcétera... una de las alternativas es adjuntar un depurador al proceso y pararlo cada X tiempo para ver como esta la memoria del proceso.

Herramientas

Como depurador he utilizado el WinDBG, diponible en las debugging tools for windows

Depurando el proceso

Compilamos y ejecutamos el ejemplo que nos pone Rodrigo.

Arrancamos WinDBG y Vamos a File .. Attach To Process y escogemos el proceso que acabamos de arrancar (buscad el pid adecuado).

image 

En la ventana de command del depurador vemos información del proceso, esta parado esperando a que introduzcamos alguna orden.

image

Por ahora no vamos a hacer nada... vamos a volver a activar el proceso para que siga corriendo un rato y empiece a cargarse la memoria, de modo que en la ventana de comandos, introducimos el comando g y damos a intro

image

Al de un minuto (por ejemplo) pulsamos ctrl + break ... asi pararemos el proceso e inspeccionaremos su estado actual. Dado que Rodrigo nos ha dicho que es un problema de memory leak... vamos a tiro hecho y buscamos información sobre objetos que se acumulan en el managed heap (normalmente no es tan fácil y hay que invertir tiempo en determinar a que tipo de problema de memoria te enfrentas)

Obteniendo la información de referencia

Lo primero que vamos a hacer es cargar la SOS.dll. Esta librería es una extensión del depurador que nos va a permitir movernos por estructuras de datos manejadas por el CLR, de modo que nos facilita la depuración de código .net. Para cargarla ejecutamos este comando

.loadby sos mscorwks

Una vez esta cargada la extensión podemos usar sus comandos. Vamos a ver cuales son los objetos que hay en el managed heap

!dumpheap -stat

De la salida del comando nos quedamos con las últimas líneas (mayor número de objetos en memoria) y guardamos los números para poder compararlos en la próxima parada

790fd8c4 5632 345980 System.String
003ec570 18 4064392 Free
7912d8f8 2514 4291592 System.Object[]
79104c38 771803 12348848 System.WeakReference

Una vez apuntado, volvemos a ejecutar el comando g y dejamos que el proceso corra durante otro rato, para ver si varían los números.

.... ( minutos musicales mientras el proceso corre ) ...

Volvemos a pararlo de nuevo con ctrl + break y volvemos a mostrar los objetos en el managed heap como antes

790fd8c4 5632 345980 System.String
003ec570 19 8258724 Free
7912d8f8 3678 8516160 System.Object[]
79104c38 1308060 20928960 System.WeakReference

Vemos que los Strings se han conservado, pero las WeakReferences han crecido ( 12348848 vs 20928960 ). Es recomendable hacer este proceso unas cuantas veces para tener mas muestras y poder establecer una tendencia :)

Una vez vemos que las weakreferences crecen, hemos de saber porqué.... a si que entramos en modo diagnostico diferencial de house...

house> ¿por qué no se liberan?

chase> será lupus?

cameron> no creo.. puede ser que alguien mantenga una referencia a ellos y por eso no los recoja el GC

house> ok.. analitica completa, TAC, rajadlo y mira las referencias que apuntan a los objetos weakreference

Para mirar las referencias, primero necesitamos saber la dirección en memoria de los objetos. Tomemos la salida del comando !dumpheap -stat, si nos fijamos en la fila relativa a las wekreferences

79104c38 1308060 20928960 System.WeakReference

El primer valor es la MethodTable del tipo, ahora que la conocemos, podemos ejecutar un comando que vuelca todos los objetos de una determinada method table

!dumpheap -mt 79104c38 

(el resultado hara scroll en pantalla varias veces, podéis pararlo con Ctr+break) Ahí tenemos todos los objetos del tipo, vamos a ver quién les esta referenciando y manteniéndolos vivos. La salida del comando tiene el siguiente formato: Dir.Objeto Dir.MethodTable Tamaño

Tomamos unas cuantas direcciones de objeto y ejecutamos el siguiente comando sobre ellas

!GCRoot dir del Objeto

Nos muestra la relación de referencias que mantienen vivo al objeto. Vemos al volcar unas cuantas que hay System.Collections.Generic.List que las referencia

0:003> !GCRoot 02497570
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 179c
Scan Thread 2 OSTHread 1624
DOMAIN(003E4AC0):HANDLE(Pinned):a13f0:Root:02df3030(System.Object[])->
01df4300(System.Collections.Generic.List`1[[System.WeakReference, mscorlib]] )->
035d52e0(System.Object[])->
02497570(System.WeakReference)

Para seguir asegurando...

Volcamos la genericList y vemos su tamaño

!do 01df4300

79102290  40009c8        c         System.Int32  1 instance  1308060 _size

dejamos correr un rato al proceso ( g ) y volvemos a comprobar el tamaño del objeto ( ejecutamos el mismo comando )

79102290  40009c8        c         System.Int32  1 instance  1524568 _size

BINGO!!!  va creciendo woooooohoooooo

Si estuviésemos en un proyecto grande habría que localizar el assembly y volcar el código para ver que pasa.... como estamos en un ejemplo pequeño es fácil... no hay ningun array en nuestro código.. a si que con ayuda de reflector miramos la clase TraceSwitch... que nos lleva a Switch... y al siguiente código (resumido)

   lock (switches)

   {

       switches.Add(new WeakReference(this));

   }

El array switches crece indefinidamente y no se liberan las weakReferences (se liberan los strings a los que apuntan las weakreferences pero no estas en sí)

y ahora qué?

Pues he estado mirando por la web y no puedo decir aun si es un bug o si hay alguna consideración que nos hayamos saltado a la hora de trabajar con el TraceSwitch, en cuanto lo sepa os lo cuento

 

<update 28/4/08>
Tenemos respuesta del grupo de producto, es un bug corregido para la próxima major relase de .net :)
</update 28/4/08>

Happy Hacking!

David Salgado