Qué pasa en Tecnología de la Información

09.05.11 VisualVM para desarrolladores Java

Este artículo es una traducción de “VisualVM for Java Development” escrito por Eric Bruno  - http://drdobbs.com/blogs/java/229403052 - Copyright 2011 UBM Techweb.  Se cuenta con la debida autorización para su traducción y utilización.-

Cuando programaba para Windows, mi grupo tenía como algo fijo apresurarse a testear, con la última versión del debugger para el kernel de Windows, cualquier versión de software. Al hacer esto, siempre se revelan muchos aspectos críticos, como perdidas de memoria y potenciales malos usos en las llamadas a las API de Windows. Uno de los fuertes al desarrollar para Windows ha sido el soporte que Microsoft ofrece en términos de herramientas y ayuda en línea.

Sin embargo, continuamente me sorprendo al ver tantos desarrolladores Java que desconocen o no desean utilizar la gran cantidad de herramientas que tienen a su disposición. Dos de las herramientas en las cuales me he apoyado a través de los años son VisualVM y VisualGC, las cuales están disponibles como herramientas "standalone" o integradas en NetBeans y Eclipse. Aunque Oracle las provee como una descarga separada, VisualVM se incluye con la última versión de Java SE 6 SDK. Busquen (jvisual.exe) en el directorio bin de su instalación local para ejecutarla en modo standalone. Si por alguna razón no se encuentra ahí, se puede descargar desde http://visualvm.java.net/

Cuando se ejecuta, VisualVM lista todas las JVM que se están ejecutando en un sistema (local o remoto si se prefiere), y permite realizar lo siguiente:

  • Visualizar información de configuración como, versión de la JVM, aplicación y flags  utilizados al arrancar, librerías/JARs importadas, ID de procesos, y todas aquellas  propiedades de sistema con las cuales la JVM está corriendo.
  • Monitorear dinámicamente la aplicación Java a medida que se ejecuta, incluyendo la actividad del Garbagge Collector, actividades de almacenamiento en el heap, clases cargadas y threads ejecutándose.
  • Monitorear el uso que cada thread de la aplicación hace de la CPU e identificar visualmente deadlocks, contención y otros aspectos potenciales de lockeo.
  • Monitorear el uso de la memoria y ver como se la va ocupando, o realizar vuelcos de  memoria heap en determinados momentos para poder compararlos y detectar potenciales perdidas de memoria.
  • Crear un perfil de tu aplicación – profile - para ver quienes dentro de ella son los que más consumen memoria y CPU de manera tal que se puedan identificar los puntos calientes  sobre los cuales realizar más debug y optimización.
  • Si ocurre que se cae la JVM, o se cuelga o se producen vuelcos de memoria, entonces puedes utilizar VisualVM para analizar las salidas de los vuelcos de memoria y ver que es lo que conduce a los mismos o puedes utilizar VisualVM para determinar que produjo la caída de la JVM.

 
Comencemos:
 
Antes, recomiendo fervientemente revisar la lista de plugins disponibles e instalar al menos, el plugin de VisualGC. Esta herramienta brinda detallada información de lo que ocurre con el GC y el uso que hace la aplicación del heap. El GC puede ser  la causa de una gran perdida de performance, aspectos de latencia, y sobretodo, comportamiento impredecible si su aplicación tiene problemas relacionados con memoria o necesita ajustes – tunning.
 
Para instalar el plugin desde el Centro de Plugins de VisualVM siga estos pasos:

  1. En el menú de VisualVM seleccione “Herramientas” - Tools – y luego “Plugins”
  2. Clickee en la solapa de “Plugins Disponibles” – Available Plugins - y seleccione el plugin que desea instalar.
  3. Clickee el botón “Instalar” – Install.

Para comenzar a debuggear con VisualVM simplemente inicie la aplicación Java y luego ejecute “visualvm” desde la línea de comandos. Asegúrese que el directorio JDK bin sea el actual o se encuentre en el path. Una vez que VisualVM se inicia, una lista de los procesos locales de la Java VM aparecen en la esquina superior izquierda, debajo del encabezado “Local” (ver siguiente figura)

También se pueden monitorear aplicaciones remotas. Para eso, debemos asegurarnos que la aplicación “stated” (jstatd.exe) esta corriendo en el host remoto, luego inicie la aplicación que se desea monitorear (no importa el orden). Dentro de VisualVM, agregue explícitamente el host remoto, seleccione la opción Plugins en Herramientas – Tools – del menú e ingrese la información del host (nombre o IP y etiqueta). Después de que se establece la conexión (chequear firewall local u otros aspectos si la conexión falla), se debería ver la lista de procesos Java corriendo en el host remoto debajo del encabezado “Remoto” – Remote -(ver figura abajo)

Se puede comenzar con la pantalla dinámica correspondiente al monitor de la aplicación. Esta pantalla se actualiza automáticamente proporcionando una vista generalizada acerca del uso de CPU, GC, uso del heap, clases cargadas e instanciadas y de los threads activos. (ver figura abajo)

Esto es solo un vista genérica; para obtener detalles, debe seleccionar algunas de las solapas superiores: Threads, Sampler, Provider y VisualGC.

Inspeccionando los Threads

VisualVM permite observar la ejecución de los threads de la aplicación (así como también threads de la JVM) a medida que la aplicación se ejecuta. El resultado que se obtiene es una lista fácil de leer con los threads que se actualiza con el paso del tiempo y los cambios de estados en los threads (ver imagen abajo). Al observar el estado de los threads, uno puede darse cuenta de cuales están bloqueando eventos u objetos loqueados debido al uso compartido con otros threads. Esto podría tratarse de un comportamiento normal o no de la aplicación y que uno no podría capturar de otra manera. Por ejemplo, yo he sido capaz de identificar thrasing (eliminación de objetos) no deseado entre threads, lockeos innecesarios, threads múltiples que actuaban como uno (secuencialmente) debido a una incorrecta sincronización, threads con deadlocks (incluyendo los threads y los objetos que loqueaban o por el cual esperaban).

Uno puede crear archivos de texto con vuelcos a medida que el proceso se ejecuta para registrar la actividad de un thread de manera tal que la misma se pueda inspeccionar más tarde. He incorporado estos vuelcos de threads en unidades de testeo para asegurarme que el código concurrente trabaja de manera como fue diseñado – algo que, de otra forma, es difícil de probar (ver figura abajo).

Utilizando VisualVM como herramienta de testeo, así como también como debugger después de que se detectó un bug, he sido capaz de eliminar bugs antes de la puesta en producción, encontrándolos de manera rápida cuando aparecían y he, de esta forma, creado testeos proactivos para prevenir futuros problemas en el sistema.

Inspeccionando el Heap y el uso de memoria:

Entonces, Java elimina todas sus preocupaciones relativas al uso de menoria. ¿Correcto?.  Equivocado. Haciendo una analogía con la mayoría de los corredores amateurs. Estos tratan de no pensar en cómo se sienten mientras están corriendo. Sin embargo, cuando le preguntaron a Bill Rodgers (ganador 4 veces de la Maratón de Boston), dijo que es en lo único en lo que piensa mientras corre.

De la misma forma, a pesar de que Java administra automáticamente la memoria, uno debe concentrarse más que nunca en el uso de la misma y de los efectos del garbagge collector en tiempo de ejecución.

Afortunadamente, VisualVM, proporciona mucha ayuda en este punto. Uno puede observar los objetos a medida que son creados y eliminados por la aplicación que se está ejecutando, tomar snapshots del heap de Java en determinados momentos para compararlos luego, o crear vuelcos del heap, ordenarlo e inspeccionarlo hasta dar con los objetos iniciales del heap. Esto le permite identificar objetos que crecerán sin control, el uso ineficiente de los recursos (tales como objetos IO relacionados, archivos, y demás), y mapeo ineficiente de datos (por ej., aquellos que se usan continuamente y crean miles de objetos String como resultado).

Uno puede crear vuelcos del heap de Java y ordenarlo por tipo de objeto (clase), por el total de objetos de una clase específica, por el objeto más grande, etc. Más adelante, uno puede inspeccionar cada objeto, para encontrar referencias a él en caso de perdidas de memoria. Esto puede ser a veces una tarea tediosa, pero la encuentro necesaria en muchas ocasiones, y estoy agradecido a VisualVM que me da la posibilidad de obtener un nivel de detalle como el que se muestra en la figura siguiente:

 

Generando el Profile de una Aplicación:

Trato de enseñarles a los nuevos programadores la importancia de tener un profile para testear el código bajo ciertas condiciones. Estas incluyen: test idle, test de stress, test para bajo uso de memoria y todos los intermedios. El resultado es una lista de objetos y métodos llamados durante la ejecución de los testeos, el número total de veces que cada método fue llamado, el tiempo total consumido por todos los métodos, y los métodos que llevaron más tiempo en ejecutarse y también el total de los mismos. Esta información es muy valiosa ya que muestra que métodos deben ser optimizados, ajustados o simplemente reescritos (ver el ejemplo abajo).

Recuerden que mucha optimización puede conducir a detectar innecesariamente bugs además resulta un esfuerzo innecesario. Sin embargo, una revisión rápida a una salida de VisualVM puede detectar potenciales problemas y resaltar aspectos de performance en poco tiempo y con poco esfuerzo.

Uso de VisualGC:

El plugin VisualGC de VisualVM puede ser una herramienta muy valiosa para algunos pero también muy abrumadora o inútil para otros. Si su software es sensible a aspectos de performance o latencia, entonces a Ud. Le interesará probablemente, lo que está pasando dentro del Java GC. VisualGC proporciona gráficos y documentación explicativa en http://java.sun.com/performance/jvmstat/visualgc.html

En resúmen, la información útil a observar incluyen a la proporción entre eden space (incluyendo espacios S0 y S1) y la vieja generación (donde residen objetos jóvenes y viejos respectivamente), el tiempo total utilizado en eliminación –collecting-  y los objetos que son descargados.

Por ejemplo, si los objetos fueran alojados directamente dentro de la vieja generación, esto podría indicar que la aplicación crea objetos muy grandes y que sería mejor fragmentarlos. También, una excesiva descarga de clases indica que la aplicación se está quedando sin memoria, situación en la que la JVM está desalojando información de clases compiladas para hacer lugar. Lo cual no solo constituye una pérdida de tiempo de procesamiento sino que significa que dichas clases podrían tener que ser cargadas (y recompiladas) nuevamente en un futuro cercano.

Sin embargo, el tunning del GC es una habilidad que se adquiere con investigación y experiencia. Si Ud. Es neófito en esto, asegúrese de utilizar VisualVM en conjunto con otros recursos Java Garbagge Collector antes de perder tiempo y esfuerzo con el GC.

Feliz codificación ( y debuggeo)!.
-EJB