En esta entrada voy a dar un repaso a las herramientas que podemos utilizar desde línea de comandos (CLI) para monitorizar el rendimiento de una Java Virtual Machine (JVM). Como complemento a esta entrada podéis leer la que publiqué hace unos días sobre como generar un Java thread dump con OpenJDK y Tomcat ya que los comandos/herramientas jstack y jps es parte importante de este tipo de monitorización, lo tenéis explicado en ese artículo con mayor detalle. También podéis revisar como acceder a una monitorización de Java a través de JXM y jConsole en modo gráfico.
Los comandos básicos que podemos utilizar para monitorizar una JVM desde CLI son los siguientes:
- jps
- jstat
- jstack
- jcmd
jps
Como ya comenté en la entrada que he citado anteriormente sobre generar un Java Thread Dump, el comando jps simplemente muestra por salida estándar los PID (lvmid) de los procesos java que hay ejecutandose en el sistema junto con el nombre de la clase que se está utilizando y sus argumentos. Es como el comando ps de *nix pero adaptado a Java.
Modo de utilización:
jps [-q] [-mlvV] [<hostid>]
Ejemplo:
# jps -mlvV 1645 org.apache.catalina.startup.Bootstrap start -Djavax.sql.DataSource.Factory=org.apache.commons.dbcp.BasicDataSourceFactory -Dcatalina.base=/usr/share/tomcat6 -Dcatalina.home=/usr/share/tomcat6 -Djava.endorsed.dirs= -Djava.io.tmpdir=/var/cache/tomcat6/temp -Djava.util.logging.config.file=/usr/share/tomcat6/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 1691 sun.tools.jps.Jps -mlvV -Dapplication.home=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71 -Xms8m
Este comando nos servirá para sacar el PID de la JVM, necesario para poder empezar la monitorización del proceso con el resto de comandos (jstat, jstack…).
jcmd
El comando jcmd (ojo, disponible a partir de JDK 7) permite consultar información sobre las JVM al igual que jps, es más, si lo ejecutamos sin parámetros la información es la misma:
# jcmd 1645 org.apache.catalina.startup.Bootstrap start 1709 sun.tools.jcmd.JCmd
# jcmd -l 1718 sun.tools.jcmd.JCmd -l 1645 org.apache.catalina.startup.Bootstrap start
Para que nos muestre información más específica y no disponible con jps utilizamos los parámeteros que nos ofrece. Si atacamos al PID de una JVM concreta sí que podemos ver información de utilidad sobre ella que no nos ofrecía jps:
$ jcmd 1645 VM.system_properties
1645:
#Sat Nov 01 10:20:55 CET 2014
java.runtime.name=OpenJDK Runtime Environment
sun.boot.library.path=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/i386
java.vm.version=24.65-b04
shared.loader=
java.vm.vendor=Oracle Corporation
java.vendor.url=http\://java.oracle.com/
path.separator=\:
java.vm.name=OpenJDK Client VM
tomcat.util.buf.StringCache.byte.enabled=true
file.encoding.pkg=sun.io
java.util.logging.config.file=/usr/share/tomcat6/conf/logging.properties
user.country=US
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=unknown
java.vm.specification.name=Java Virtual Machine Specification
user.dir=/usr/share/tomcat6
java.runtime.version=1.7.0_71-mockbuild_2014_10_17_21_54-b00
java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment
java.endorsed.dirs=
os.arch=i386
javax.accessibility.assistive_technologies=org.GNOME.Accessibility.JavaBridge
java.io.tmpdir=/var/cache/tomcat6/temp
line.separator=\n
java.vm.specification.vendor=Oracle Corporation
java.util.logging.manager=org.apache.juli.ClassLoaderLogManager
java.naming.factory.url.pkgs=org.apache.naming
os.name=Linux
sun.jnu.encoding=UTF-8
java.library.path=/usr/java/packages/lib/i386\:/lib\:/usr/lib
java.specification.name=Java Platform API Specification
java.class.version=51.0
sun.management.compiler=HotSpot Client Compiler
os.version=2.6.32-279.el6.i686
user.home=/usr/share/tomcat6
catalina.useNaming=true
user.timezone=Europe/Madrid
java.awt.printerjob=sun.print.PSPrinterJob
file.encoding=UTF-8
java.specification.version=1.7
catalina.home=/usr/share/tomcat6
java.class.path=\:/usr/share/tomcat6/bin/bootstrap.jar\:/usr/share/tomcat6/bin/tomcat-juli.jar\:/usr/share/java/commons-daemon.jar
user.name=tomcat
java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory
package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.
javax.sql.DataSource.Factory=org.apache.commons.dbcp.BasicDataSourceFactory
java.vm.specification.version=1.7
sun.java.command=org.apache.catalina.startup.Bootstrap start
java.home=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre
sun.arch.data.model=32
user.language=en
java.specification.vendor=Oracle Corporation
awt.toolkit=sun.awt.X11.XToolkit
java.vm.info=mixed mode, sharing
java.version=1.7.0_71
java.ext.dirs=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/ext\:/usr/java/packages/lib/ext
sun.boot.class.path=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/resources.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/rt.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/sunrsasign.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/jsse.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/jce.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/charsets.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/rhino.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/jfr.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/classes
server.loader=
java.vendor=Oracle Corporation
catalina.base=/usr/share/tomcat6
file.separator=/
java.vendor.url.bug=http\://bugreport.sun.com/bugreport/
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
sun.font.fontmanager=sun.awt.X11FontManager
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.endian=little
package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.,sun.beans.
sun.cpu.isalist=
Ya tenemos un volcado con un montón de información de la JVM. Vemos información relacionada con:
- VM.native_memory
- ManagementAgent.stop
- ManagementAgent.start_local
- ManagementAgent.start
- Thread.print
- GC.class_histogram
- GC.heap_dump
- GC.run_finalization
- GC.run
- VM.uptime
- VM.flags
- VM.system_properties
- VM.command_line
- VM.version
Podemos invocar por separado cada propiedad para evitar un volcado tan grande de información. Si queremos ver, por ejemplo, información de los Threads en ejecución ejecutaríamos lo siguiente. Si os fijáis, es lo mismo que la ejecución del jstack, que utilizabamos para generar un thread dump:
$ jcmd 1645 Thread.print 1645: 2014-11-01 10:25:00 Full thread dump OpenJDK Client VM (24.65-b04 mixed mode, sharing): "Attach Listener" daemon prio=10 tid=0xb763b800 nid=0x6f6 runnable [0x00000000] java.lang.Thread.State: RUNNABLE "TP-Monitor" daemon prio=10 tid=0xb763a000 nid=0x69a in Object.wait() [0xb47ba000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on (a org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable) at org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable.run(ThreadPool.java:565) - locked (a org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable) at java.lang.Thread.run(Thread.java:745) "TP-Processor4" daemon prio=10 tid=0xb7638400 nid=0x699 runnable [0xb480b000] java.lang.Thread.State: RUNNABLE at java.net.PlainSocketImpl.socketAccept(Native Method) at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:398) at java.net.ServerSocket.implAccept(ServerSocket.java:530) at java.net.ServerSocket.accept(ServerSocket.java:498) at org.apache.jk.common.ChannelSocket.accept(ChannelSocket.java:307) at org.apache.jk.common.ChannelSocket.acceptConnections(ChannelSocket.java:661) at org.apache.jk.common.ChannelSocket$SocketAcceptor.runIt(ChannelSocket.java:872) at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690) at java.lang.Thread.run(Thread.java:745) "TP-Processor3" daemon prio=10 tid=0xb7636800 nid=0x698 in Object.wait() [0xb485c000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on (a org.apache.tomcat.util.threads.ThreadPool$ControlRunnable) at java.lang.Object.wait(Object.java:503) at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:662) - locked (a org.apache.tomcat.util.threads.ThreadPool$Co [...]
Las posibilidades son muy amplias, también podemos, por ejemplo, visualizar un volcado de todos los contadores relacionados con el rendimiento de la JVM:
$ jcmd 1645 GC.heap_dump PerfCounter.print 1645: java.ci.totalTime=1455540 java.cls.loadedClasses=941 java.cls.sharedLoadedClasses=651 java.cls.sharedUnloadedClasses=0 java.cls.unloadedClasses=0 java.property.java.class.path=":/usr/share/tomcat6/bin/bootstrap.jar:/usr/share/tomcat6/bin/tomcat-juli.jar:/usr/share/java/commons-daemon.jar" java.property.java.endorsed.dirs="" java.property.java.ext.dirs="/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre/lib/ext:/usr/java/packages/lib/ext" java.property.java.home="/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71/jre" java.property.java.library.path="/usr/java/packages/lib/i386:/lib:/usr/lib" java.property.java.version="1.7.0_71" java.property.java.vm.info="mixed mode, sharing" java.property.java.vm.name="OpenJDK Client VM" java.property.java.vm.specification.name="Java Virtual Machine Specification" java.property.java.vm.specification.vendor="Oracle Corporation" java.property.java.vm.specification.version="1.7" java.property.java.vm.vendor="Oracle Corporation" java.property.java.vm.version="24.65-b04" java.rt.vmArgs="-Djavax.sql.DataSource.Factory=org.apache.commons.dbcp.BasicDataSourceFactory -Dcatalina.base=/usr/share/tomcat6 -Dcatalina.home=/usr/share/tomcat6 -Djava.endorsed.dirs= -Djava.io.tmpdir=/var/cache/tomcat6/temp -Djava.util.logging.config.file=/usr/share/tomcat6/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager" java.rt.vmFlags="" java.threads.daemon=12 java.threads.live=13 java.threads.livePeak=13 java.threads.started=13 sun.ci.compilerThread.0.compiles=309 sun.ci.compilerThread.0.method="" sun.ci.compilerThread.0.time=6089 sun.ci.compilerThread.0.type=1 sun.ci.lastFailedMethod="" sun.ci.lastFailedType=0 sun.ci.lastInvalidatedMethod="" sun.ci.lastInvalidatedType=0 sun.ci.lastMethod="java/lang/String getBytes" sun.ci.lastSize=27 sun.ci.lastType=1 sun.ci.nmethodCodeSize=228688 sun.ci.nmethodSize=575040 sun.ci.osrBytes=0 sun.ci.osrCompiles=0 sun.ci.osrTime=0 sun.ci.standardBytes=52071 sun.ci.standardCompiles=309 sun.ci.standardTime=1455540 sun.ci.threads=1 sun.ci.totalBailouts=0 sun.ci.totalCompiles=309 sun.ci.totalInvalidates=0 sun.classloader.findClassTime=1961005332 sun.classloader.findClasses=251 sun.classloader.parentDelegationTime=899720500 sun.cls.appClassBytes=1329461 sun.cls.appClassLoadCount=488 sun.cls.appClassLoadTime=2268616 sun.cls.appClassLoadTime.self=1166487 sun.cls.classInitTime=5150627 sun.cls.classInitTime.self=3333236 sun.cls.classLinkedTime=1528211 sun.cls.classLinkedTime.self=144025 sun.cls.classVerifyTime=1313279 sun.cls.classVerifyTime.self=483593 sun.cls.defineAppClassTime=655180 sun.cls.defineAppClassTime.self=69185 sun.cls.defineAppClasses=271 sun.cls.initializedClasses=1181 sun.cls.isUnsyncloadClassSet=0 sun.cls.jniDefineClassNoLockCalls=0 sun.cls.jvmDefineClassNoLockCalls=38 sun.cls.jvmFindLoadedClassNoLockCalls=1075 sun.cls.linkedClasses=1420 sun.cls.loadInstanceClassFailRate=0 sun.cls.loadedBytes=1131600 sun.cls.lookupSysClassTime=2295064 sun.cls.methodBytes=800392 sun.cls.nonSystemLoaderLockContentionRate=1 sun.cls.parseClassTime=1700604 sun.cls.parseClassTime.self=1159188 sun.cls.sharedClassLoadTime=1412946 sun.cls.sharedLoadedBytes=816344 sun.cls.sharedUnloadedBytes=0 sun.cls.sysClassBytes=2491030 sun.cls.sysClassLoadTime=3954269 sun.cls.systemLoaderLockContentionRate=0 sun.cls.time=8982118 sun.cls.unloadedBytes=0 sun.cls.unsafeDefineClassCalls=20 sun.cls.verifiedClasses=824 sun.gc.cause="No GC" sun.gc.collector.0.invocations=5 sun.gc.collector.0.lastEntryTime=11849108 sun.gc.collector.0.lastExitTime=11871701 sun.gc.collector.0.name="Copy" sun.gc.collector.0.time=192835 sun.gc.collector.1.invocations=0 sun.gc.collector.1.lastEntryTime=0 sun.gc.collector.1.lastExitTime=0 sun.gc.collector.1.name="MSC" sun.gc.collector.1.time=0 sun.gc.generation.0.agetable.bytes.00=0 sun.gc.generation.0.agetable.bytes.01=393216 sun.gc.generation.0.agetable.bytes.02=0 sun.gc.generation.0.agetable.bytes.03=0 sun.gc.generation.0.agetable.bytes.04=0 sun.gc.generation.0.agetable.bytes.05=0 [...]
La información puede ser abrumadora, lo sé, así que lo recomendable es localizar los parámetros y valores que realmente necesitamos para nuestra monitorización del rendimiento de la JVM y procesarlos como estimemos oportuno (scripts, gráficas, etc).
jstat
El comando jstat muestra información detallada y estadísticas de la JVM.
Lo más básico a conocer es la forma de mostrar información sobre el Garbage Collector (GC). Para ello se utiliza el parámetro -gctuil junto con el PID de la JVM que obtenemos con jps o jcmd:
$ jstat -gcutil 1645 S0 S1 E O P YGC YGCT FGC FGCT GCT 0.00 100.00 44.35 38.48 31.63 5 0.193 0 0.000 0.193
Para visualizar la información a tiempo real en un intervalo específico de tiempo añadimos como parámetro el tiempo en milisegundos. En el siguiente ejemplo se muestra la información cada 2 segundos para la JVM con el PID 1645:
$ jstat -gcutil 1645 2000 S0 S1 E O P YGC YGCT FGC FGCT GCT 0.00 100.00 44.35 38.48 31.63 5 0.193 0 0.000 0.193 0.00 100.00 44.35 38.48 31.63 5 0.193 0 0.000 0.193 0.00 100.00 44.35 38.48 31.63 5 0.193 0 0.000 0.193 [...]
La explicación de los valores de cada columna son los siguientes:
- S0: Survivor space 0. Porcentaje de utilización sobre la capacidad total del espacio de memoria.
- S1: Survivor space 1. Porcentaje de utilización sobre la capacidad total del espacio de memoria.
- E: Eden space. Porcentaje de utilización sobre la capacidad total del espacio de memoria.
- O: Old space. Porcentaje de utilización sobre la capacidad total del espacio de memoria.
- P: Permanent space. Porcentaje de utilización sobre la capacidad total del espacio de memoria.
- YGC: número de eventos de «young generation GC» generados.
- YGCT: Tiempo de garbage collection utilizada por los eventos «Young generation».
- FGC: número total de eventos en el GC.
- FGCT: tiempo utilizado para un GC completo.
- GCT: tiempo total utilizado para eventos de GC.
jstack
Al comienzo del artículo tenéis el link a la entrada en la que hablo sobre jstack. Básicamente jstack permite generar un «thread dump» de Java, es decir, un volcado de todos los hilos que hay en ejecución en una JVM (Java Virtual Machine).
$ jstack 1645 | more 2014-11-01 10:44:01 Full thread dump OpenJDK Client VM (24.65-b04 mixed mode, sharing): "Attach Listener" daemon prio=10 tid=0xb763b800 nid=0x6f6 runnable [0x00000000] java.lang.Thread.State: RUNNABLE "TP-Monitor" daemon prio=10 tid=0xb763a000 nid=0x69a in Object.wait() [0xb47ba000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on (a org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable) at org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable.run(ThreadPool.java:565) - locked (a org.apache.tomcat.util.threads.ThreadPool$MonitorRunnable) at java.lang.Thread.run(Thread.java:745) "TP-Processor4" daemon prio=10 tid=0xb7638400 nid=0x699 runnable [0xb480b000] java.lang.Thread.State: RUNNABLE at java.net.PlainSocketImpl.socketAccept(Native Method) at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:398) at java.net.ServerSocket.implAccept(ServerSocket.java:530) at java.net.ServerSocket.accept(ServerSocket.java:498) at org.apache.jk.common.ChannelSocket.accept(ChannelSocket.java:307) at org.apache.jk.common.ChannelSocket.acceptConnections(ChannelSocket.java:661) at org.apache.jk.common.ChannelSocket$SocketAcceptor.runIt(ChannelSocket.java:872) at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690) at java.lang.Thread.run(Thread.java:745) [...]
El dump se genera en STDOUT así que es conveniente redirigirlo a un archivo:
$ jstack 1645 > dump.out
Este comando es muy importante para visualizar si hay threads bloqueados o esperando ya que nos muestra el estado del thread: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING junto con la excepción e información del hilo.
En resumen, conociendo estos comandos, su utilización básica y combinandolos con herramientas gráficas como jConsole y JMX podemos tener una buena visión del estado, rendimiento y propiedades de la JVM y el funcionamiento del servidor de aplicaciones.
En referencia al jstack, ¿sólo tiene sentido cuando usas Runnable o también tiene cabida, por ejemplo, los AsyncTask de Android?
Hola Manoel,
Ahí me pillas, nunca he trabajado con AsyncTask de Android. Lo siento.
Saludos