12 de marzo de 2010

Más herramientas para análisis del heap de Java

Como resultado final del análisis de memoria que he comentado en los anteriores artículos, voy a hacer una recopilación del metodo de análisis empleado y las herramientas que nos han sido útiles.

Paso 1. Reproducir el problema

Para hallar un goteo de memoria a partir de la version 5 de Java, lo más comodo es arrancar el servidor de aplicaciones/servlets con los siguientes parámetros:

-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=9696 
-Dcom.sun.management.jmxremote.authenticate=false 
-Dcom.sun.management.jmxremote.ssl=false

Estos parámetros hay que pasárselos a la JVM del servidor. Dependiendo del servidor esto se hará en un fichero u otro de configuración. Por ejemplo, en Tomcat basta con definir una variable de entorno llamada JAVA_OPTS antes de lanzar el Tomcat.

Una vez lanzado el servidor ejecutamos el programa jconsole, que viene con el JDK y nos conectamos al servidor. Podemos identificarlo por el PID si hemos lanzado el jconsole en la misma máquina que el servidor o conectarnos en remoto poniendo la IP del servidor más el puerto que hemos definido en los parámetros (en nuestro caso el 9696).

Con el jconsole podemos visualizar en tiempo real el consumo del heap y forzar a la JVM a que haga recolecciones de basura. Ahora solo queda ir navegando por la aplicación hasta identificar la operativa que causa el gasto/goteo de memoria. Como esto depende de la aplicación tendreis que ser suficientemente ingeniosos como para poder reproducir el problema y detectar donde se produce.

Paso 2. Encontrar la causa del problema

Una vez que logramos reproducir el goteo de memoria, ya solo nos queda averiguar que es lo que no se está limpiando de la memoria. Para ello, lo mas comodo es hacer un volcado del heap de Java y luego analizarlo para ver que objetos consumen más. Para ello vamos a usar cuatro herramientas:

  • jmap (viene con el JDK): hace volcados del heap del proceso JVM que le digamos
  • gcore (solo en Unix): hace volcados nativos de la memoria de un proceso (los famosos core dumps)
  • HeapAnalyzer (de IBM): analiza los volcados de memoria de forma gráfica
  • Memory Analyzer Tool (de Eclipse): analiza los volcados de memoria de forma gráfica

Para hacer un volcado del heap de la JVM basta con ejecutar (con el servidor corriendo, obviamente):

jmap -dump:format=b,file=dump.hprof PID_del_servidor

Así de simple. Esto nos generará un fichero llamado dump.hprof que podremos analizar con el HeapAnalyzer de IBM (que se puede descargar de aquí) o con el Memory Analyzer Tool de Eclipse (que se puede descargar de aquí). Cualquiera de estas dos herramientas es capaz de mostrarnos un árbol con todos los objetos cargados en la JVM y las dependencias entre ellos (quién contiene a quién). Con esa información y sabiendo como está estructurada la aplicación podremos saber en que se pierde la memoria.

Paso 3 (opcional). Modo pánico: ¡no funciona el jmap!

Los observadores os habreis dado cuenta de que no he dicho nada de gcore. Eso es porque, en principio, esa herramienta no nos hace falta. Sin embargo, es posible que el jmap no os funcione bien (a nosotros nos daba una NullPointerException al usarlo) porque es una herramienta experimental. Entonces, hay una solucion de emergencia que consiste en ejecutar:

gcore PID_del_servidor

Esto nos generará un "core dump" de Unix que podremos pasarle al jmap para que nos genere el dump.hprof, como si nos hubiesemos conectado al servidor con el jmap. Para esto hay que ejecutar el comando:

jmap -dump:format=b,file=dump.hprof $JAVA_HOME/java Archivo_del_core_dump

Esto lanzara la JVM con el debugger de Unix enganchado a ella, cargará el core dump, y después el jmap volcará el heap de la JVM. Despues, analizaremos el dump del heap con las dos herramientas de análisis.

Paso 4 (todavía más opcional). Modo pánico*2: ¡no funciona el jmap! ¡no tengo el gcore!


Si estais en Windows o en Unix en donde no existe gcore y no os funciona el jmap, entonces no está todo perdido. Aún nos queda una bala en la recámara consistente en lanzar el servidor pasándole el siguiente parámetro:


-XX:+HeapDumpOnOutOfMemoryError


Este parámetro causa la generación de un volcado automático cuando se produce un OutOfMemoryError. Una vez se genere el volcado, lo podeis analizar con las herramientas habituales.

En fin, eso es todo. Espero que este pequeño artículo os sea de ayuda a la hora de diagnosticar un goteo de memoria o un OutOfMemoryError.

Seguiremos informando.


5 comentarios:

  1. Después de ver lo que habeis hecho, unos comentarios:

    1) Espero que la IP de la máquina donde teneis ese servidor de aplicaciones sea privada por que si no, cualquiera podrá acceder por JMX y jugar al reinicio con ella :). Nosotros lo que hacemos es ponerle al menos un usuario/clave para que ni siquiera desde dentro de la red interna nos puedan hacer la puñeta. Es muy sencillo, probadlo.

    2)Te recomiendo de nuevo alguna herramienta como el YourKit Java Profiler por que los heap-dumps los haces directamente desde la herramienta y ya puedes ver inmendiatamente los resultados. No sólo eso, si no que te permite hacer varios y luego compararlos entre sí. Ahí es donde "cantan" los consumos de memoria mucho y puedes apreciar más facilmente donde se pierte "el aceite".

    3) El primer paso que suelo hacer yo es conectarme con el JConsole, que viene ahora directamente con el JDK aunque la version "standalone" siempre va algo más avanzada, y simplemente mirar si el consumo va creciendo, si después de un GC se recupera... si ahí veo que la cosa sube y no para, entonces sí que me voy a por los HeapDumps. Teniendo el jmxremote habilitado conectarse es un minuto y al menos puedes ver el estado del Heap, del PermGen que tambien es importante etc.

    S!

    ResponderEliminar
  2. Bueno, lo de usuario y contraseña es una buena puntualizacion. En realidad es que el JMX no es buena idea usarlo en produccion. Es mejor reproducir el error en una maquina de desarrollo o similar y probar ahi.

    En caso de no poderse se haria en produccion, con toda la seguridad necesaria (usuarios, passwords, "fireguoles", etc.) pero yo prefiero no andar tocando mucho los sistemas de produccion, maxime cuando no los administro yo. Ya me entiendes...

    Y en cuanto al punto 2 y 3 totalmente de acuerdo, pero como he comentado en otro post, estas pruebas las estamos haciendo en un entorno con JDK 1.4 y Sun One WebServer y ahi no podemos ejecutar la JConsole ni nada.

    Para conectar la JConsole al JDK1.4 hay que desplegar el JMX como librerias de usuario y me dio tanta pereza :-) que opte por usar otro sistema.

    ResponderEliminar
  3. Ah, es que como empezabas hablando de habilitar el JMX, pensé que ya partías de la base de tenerlo activado. Si no, pues sí, toca ajo y agua. Yo conseguí estas navidades que me pasaran a un entorno de Linux+ Java 6 en una máquina donde me torturaban con True64 Unix + Java 4 (además el de DEC, puagh) y no veas que alivio.

    A mi como me toca administrar los servidores de aplicaciones, los tengo habilitados ahora con JMX y seguridad, al menos para mirarlos de vez en cuando a ver como les va la memoria. De todas formas reiniciamos todos los servidores una vez al día, por "seguridad".

    ResponderEliminar
  4. No te quejes, podia haber sido peor. ¡Podia haber sido un AIX! (Si es que a eso se le puede llamar Unix, claro) :-)

    En realidad hice pruebas con JMX en mi maquina para ver como se comportaba la aplicacion. Y luego al llegar al cliente vi lo de que no funcionaba en JDK 1.4.

    Otra cosa que conviene advertir de lo del JMap, es que te deja cuajada la maquina mientras hace el dump. Asi que para dumps grandes, mejor no usarlo en produccion en horas puntas.

    ResponderEliminar
  5. Veo tu AIX, que utilicé, sí, y también bajo el nombre de HP-UX, y lo subo a OpenVMS, toma ya! Un S.O. donde el formato de particiones por defecto no admite mas de 8 subdirectorios anidados así que cuando algún .jar se descompilá para ejecutarse...

    En fin, menos mal que ya sólo es un recuerdo para mi, aunque se sigue usando para otras cosas...

    Yo ahora tengo una JVM que a veces deja de responder, sin mensajes de log, sin trazas... ni siquiera sé si es que no responde por la red por que está cuajado o por que falla la red. Y siempre lo hace cuando no hay nadie... ains que dolor.

    ResponderEliminar