17 diciembre 2008

Monitoreo de aplicaciones en Java

A través de la consultora en la que estoy trabajando estuve analizando en los últimos meses problemas de arquitectura y performance de una aplicación J2EE.

Durante esta tarea descubrí algunas herramientas incluidas en el JDK (a partir de la versión 1.5), que son de muchísima utilidad para encontrar problemas de performance.

El primer paso: ¿Que proceso del SO es el de nuestra aplicación?

La forma más sencilla de ver que una aplicación esta consumiendo más recursos de los que debe, es usar alguna herramienta del sistema operativo como el Administrador de tareas en Windows o top en Unix.

El problema es que si tenemos varios procesos Java cuesta distinguir cual es cual. Para facilitar esta tarea el JDK incluye una herramienta llamada jps (similar al ps de Unix), por ejemplo:

jps -lm
(-l muestra el main class usado, y -m muestra los argumentos pasados al main)

Monitorear la Virtual Machine

La VM incluye también servicios de JMX para monitorear su funcionamiento: uso de threads y memoria.

Hay dos herramientas para hacer esto: JConsole y VisualVM (incluida con en el JDK 6 a partir del update 10). La primera solo provee información mediante los servicios de JMX que expone la VM. Mientras que el segunda puede además proveer información de profiling más detallada.

Ambas permiten conectarse de forma automática a una VM local (siempre y cuando la VM sea de una versión 1.5 o superior). Pero también hay varias maneras de conectarse remotamente:

Mediante JMX

Es necesario activar primero el acceso remoto a los servicios de JMX. Si no interesa tener ningún tipo de autenticación la forma más sencilla es agregar a la VM los siguientes parámetros de inicio:

-Dcom.sun.management.jmxremote.port=[portnum]
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

Luego simplemente hay que indicar el IP y puerto al iniciar JConsole.

Usando jstatd

jstatd es un servidor de RMI que expone información sobre las VMs corriendo en el host actual. Para usarlo hay que primero crear un archivo de policy para establecer los permisos de este proceso.

Una forma rápida de crear el archivo de policy es usar la herramienta policytool y crear un archivo que permita hacer todo (ejecutar policytool, luego Add Policy Entry -> Add Permision -> seleccionar All Permision -> Ok y finalmente guardar el archivo), por ejemplo:

all.policy:

grant {
permission java.security.AllPermission;
};

Una vez creado el archivo de policy, jstatd se inicia de la siguiente manera:

jstatd -J-Djava.security.policy=all.policy

En una larga línea de bash:
echo 'grant { permission java.security.AllPermission; };' > all.policy; jstatd -J-Djava.security.policy=all.policy

Una vez levantado jstatd, es posible usar VisualVM para conectarse con la VM.

Análisis post-mortem

A veces (especialmente en producción) no es posible monitorear la VM en real-time. Para esos casos hay muy buenos análisis que se pueden hacer sobre una "foto" de la ejecución.

Encontrar Leaks

El proyecto Eclipse tiene una herramienta excelente para encontrar leaks: Memory Analyser Tool.

Esta herramienta lee un memory dump y lo analiza, permitiendo por ejemplo ver que tipo de objetos ocupan mas memoria, o recorrer el grafo de alcance del GC.

Lo bueno es que el dump se puede hacer sin problemas en un VM corriendo en producción (lo malo es que el dump ocupa mucho espacio en disco). Hay varias formas de obtenerlo:

Usando jmap (incluido en el JDK 6):
jmap -dump:format=b,file=HeapDump.hprof pid

Pasarle un parámetro a la VM para que haga el dump en caso de un OutOfMemoryException:
-XX:HeapDumpPath=/path -XX:+HeapDumpOnOutOfMemoryError

También es posible obtener el dump en el JDK 1.4.2, usando:
-XX:+HeapDumpOnCtrlBreak
y luego haciendo: kill -3 pid

(en este caso, no es posible indicar el path donde va a parar el dump... se guarda con extensión .hprof en el directorio donde se inicio la JVM).

Una tercer forma de obtener el dump es usando JConsole accediendo al MBean: com.sun.management.HotSpotDiagnostic y ejecutando la operación dumpHeap (el primer parámetro es el nombre del archivo donde se hara el dump).

Encontrar problemas de Threading

Se puede hacer un thread dump de dos maneras.

Cuando se usa kill -3 pid el proceso de la VM imprime el thread dump en el standard output de la VM en ejecución (notar que kill -3 no mata el proceso, lo malo es que el standard output de la VM puede estar en cualquier lado dependiendo de cómo se inicio el proceso, o incluso perderse en la nada si no se re direcciono a un archivo).

La otra forma más conveniente es usar jstack pid./p>

Una vez obtenido el thread dump se puede usar TDA (Thread dump analyzer): Esta herramienta parsea el thread dump y muestra la información de una forma más conveniente.

La información más interesante que se puede sacar del thread dump, es ver que threads están a la espera de un monitor (en mi caso esta información me sirvió para encontrar un monitor loco en el método toString() de Timestamp... malditas globales: Nota al margen toString() de la clase Date y sus derivados usa un calendario global que esta protegido con u synchronized, por eso hay que tener cuidado, escribir a un log un date.toString() puede dar problemas de threads esperando por entrar en la sección critica… es preferible usar SimpleDateFormat)

Conclusiones

Bueno este fue un post bastante técnico y especifico en comparación a los que venia haciendo. Tenia ganas de escribirlo por una razón: es probable que en unos meses me olvide de todo esto :) por eso quería tener la información a mano en el Blog.

En mi caso la herramienta de análisis de memoria de Eclipse fue un "golazo"! Puede hacer el dump en producción y encontrar el leak en segundos.

5 comentarios:

  1. groso! gracias por la información diegote!

    ResponderBorrar
  2. Me parece excelente tu post, pero tengo una duda: voy a empezar a monitorear remotamente la JVM de varias PCs, pero quiero saber si el puerto a usar en la siguiente instrucción es arbitrario o existe un puerto específico:

    -Dcom.sun.management.jmxremote.port=[portnum]


    Gracias por la info

    ResponderBorrar
  3. @Sergio
    Desconozco si hay un puerto especifico.

    Además del puerto hay que establecer la autenticación (que es lo que hacen el resto de las propiedades que describo en el post).

    Para más info mira:
    http://java.sun.com/j2se/1.5.0/docs/guide/management/agent.html

    ResponderBorrar
  4. respecto a "es preferible usar SimpleDateFormat", esta clase provoca problemas de memoria debido a que tiene métodos y variables estáticas. Como alternativa existe la librería JodaTime que evita estos problemas con el manejo de fechas.
    http://joda-time.sourceforge.net/

    ResponderBorrar
  5. Raul gracias por el comentario, conozco Joda Time... cuando escribi el post el comentario era porque SqlDate.toString tambien tiene una variable estatica en una sección critica para el locale :(

    Supongo que si SimpleDateFormat tambien tiene algo asi... bueno otra razon más para no usar el API de fechas que viene con el JDK.

    Saludos y gracias por comentar!

    ResponderBorrar