viernes, diciembre 22, 2006

 

HttpUnit (y 2): esa fuga de memoria (memory leak)...

El concepto de recolector de basura de java tiene muchas ventajas, y algún inconveniente: las fugas de memoria (memory leaks). En java el programador no tiene que preocuparse de liberar la memoria de los objetos creados. Cuando éstos dejan de ser necesarios el recolector libera su memoria automáticamente. El problema es el concepto "ser necesario". Puede que nosotros ya no necesitemos ciertos objetos pero que éstos estén almacenados en alguna colección (ArrayList por ejemplo), por lo que el recolector no los liberará.

Otros lenguajes de programación como C o C++ no tienen este problema, pero claro, ello se debe a que no tienen recolector de basura, por lo que tienen otros problemas mucho mayores (punteros mal gestionados etc.).

Pues bien, en el desarrollo de una aplicación Swing que emplea la librería HttpUnit de la que ya hablé en un post anterior tuve la "suerte" de toparme con una fuga de memoria. Esto se hizo evidente cuando el jconsole me proporcionó la siguiente información:



En ella observamos que el consumo de memoria crece de forma constante e indefinida.

Al intentar analizar cuál podía ser la causa de la fuga en mi cerebro se entabló una lucha interna entre mi orgullo y mi prudencia. El uno decía "está claro, tu código no tiene ningún problema, es culpa de alguna librería". La otra decía "las librerías que estás utilizando las usan muchas otras personas, seguro que el problema es de tu código". Ya sabeis, el típico síndrome de "¿a quién le echamos la culpa?" tan famoso como inútil en la mayoría de los casos ;-)

Después de analizar (sin pararme demasiado tampoco) mi código, parecía que no había cometido ninguno de los típicos errores que favorecen las fugas de memoria, por lo que decidí, adoptando una actitud más positiva y productiva, utilizar una potente herramienta para identificar el culpable (para solucionar el problema, no para señalarle con el dedo, jeje): el increible profiler del NetBeans.

La configuración del profiler usada fue la siguiente:



En ella observamos que he seleccionado la opción que hace que el profiler sólo muestre los objetos que están "vivos" (Record both object creation and garbage collection) en cada momento. Al principio me llevé una pequeña sorpresa cuando observé que el total de memoria total usada por la JVM era mucho mayor que la que el profiler me presentaba en su categorización de uso de la memoria por clases. Preguntando por la razón de esta inconsistencia usando la lista de correo del profiler, Gregg Sporar, un programador veterano de Sun, actualmente "Technical Evagelist" del Netbeans, que conocí en Madrid en los Sun Tech Days de este año y que ya me echó una mano en otra ocasión, me respondió en la lista explicando que el parámetro "Track every 10 obect allocations" hace que el profiler sólo registre el 10% de todos los objetos creados. Esto hace que el profiling no afecte demasiado al rendimiento de la aplicación, aunque dicho 10% normalmente sea más que suficiente para identificar el patrón de fuga de memoria.

En mi caso así fue, tras unas tres horas de ejecución realicé una captura (snapshot) del uso de memoria y obtuve el siguiente resultado:



Claramente el problema se debía a la proliferación de arrays de caracteres. Lo más probable es que se trataran de String's (una String de java usa un char[] internamente para el almacenamiento de los caracteres). Pinchando con el botón derecho del ratón sobre la primera fila de la captura (la de los char[]) y seleccionando la opción Show Allocation Stack Traces, que nos permite ver quién/quienes crearon dichos objetos obtuve la clave del asunto:



El problema estaba en el método

handleScriptException( Exception e, String badScript )

de la clase

com.meterware.httpunit.javascript.JavaScript$JavaScriptEngine

Viendo el código fuente descubrí que el HtppUnit guarda todas las excepciones lanzadas por su intérprete de JavaScript (rhino) en un ArrayList, y que dicho array debe ser "limpiado" manualmente por el usuario de la librería, usando la función

HttpUnitOptions.clearScriptErrorMessages();

Los siguientes pasos fueron inmediatos:
  1. Modificar el código para llamar a dicha función periódicamente
  2. Enviar un correo a la lista httpunit-develop@lists.sourceforge.net comentando este problema.
(El segundo punto es verdaderamente importante para cualquier herramienta open source. Os animo a todos a que, cuando os encontreis con un problema, si conseguís resolverlo informeis a la "comunidad" para que todos podamos beneficiarnos de esa solución)

Una vez realizada la modificación mencionada, reiniciamos la aplicación y el jconsole nos proporcionó esta bella panorámica:

Adios a la fuga de memoria!

Feliz Navidad a todos.

Comments:
Como diría el Sr. Burns... excelente ;)
 
Hola! I have a question about this - would you please send me an email: gregg dot sporar at sun dot com. Thanks!
 
Joder, tengo un problema con HttpUnit, busco en el puto google y me encuentro con este tío... ¡Me cago en mi mala suerte! ;P

Un saludo
David (cayendo en el lado oscuro de la fuerza)
 
Publicar un comentario en la entrada



<< Home

This page is powered by Blogger. Isn't yours?