La plataforma Java® 2 proporciona muchas más capacidades de perfilado que las anteriormente disponibles y el análisis de estos datos generado se ha hecho más fácil por la emergencia de un "Heap Analysis Tool" (HAT). Esta herramienta, como implica su nombre, nos permite analizar los informes de perfiles del heap. El heap es un bloque de memoria que la JVM usa cuando se está ejecutando. La herramienta de análisis de heap nos permite generar informes de objetos que fueron usado al ejecutar nuestra aplicación. No sólo podemos obtener un listado de los métodos llamados más frecuentemente y la memoria usada en llamar a esos métodos, pero también podemos seguir los picos de memeoria. Los picos de memoria pueden tener un significante impacto en el rendimiento.
java -Xrunhprof:help Hprof usage: -Xrunhprof[:help]|[<option>=<value>, ...]
| Nombre de Opción y Valor | Descripción | Por Defecto |
| -------------------------- | --------------- | --------------- |
| heap=dump|sites|all | heap profiling | all |
| cpu=samples|times|old | CPU usage | off |
| monitor=y|n | monitor contention | n |
| format=a|b | ascii or binary output | a |
| file=<file> | write data to file | java.hprof(.txt for ascii) |
| net=<host>:<port> | send data over a socket | write to file |
| depth=<size> | stack trace depth | 4 |
| cutoff=<value> | output cutoff point | 0.0001 |
| lineno=y|n | line number in traces | y |
| thread=y|n | thread in traces? | n |
| doe=y|n | dump on exit? | y |
Example: java -Xrunhprof:cpu=samples,file=log.txt,
depth=3 FooClass
La siguiente invocación crea una fichero de texto que podemos ver sin la herramienta de análisis de heap llamado java.hprof.txt cuando el programa genera un seguimiento de pila o sale. Se utiliza una invocación diferente para crear un fichero binario para usarlo con la herramienta de análisis de heap:
La opción de perfil literalmente hace un diario de cada objeto creado en el heap, por incluso cuando arrancamos y paramos el pequeño progeama TableExample3 resulta un ficheo de informe de cuatro megabytes. Aunque la herramienta de análisis de heap usa una versión binaria de este fichero y proporciona un sumario, hay algunas cosas rápidas y fáciles que podemos aprender desde el fichero de texto sin usar la herramienta de análisis de heap.java -Xrunhprof TableExample3 d:\jdk12\demo\jfc\Table> java -Xrunhprof TableExample3 Dumping Java heap ... allocation sites ... done.
Nota: Para listar todas las opciones disponibles, usamos
java -Xrunhprof:help
SITES BEGIN (ordered by live bytes)
Sun Dec 20 16:33:28 1998
| percent | live | alloc'ed | stack | class | ||||
| rank | self | accum | bytes | objs | bytes | objs | trace | name |
| 1 | 55.86% | 55.86% | 826516 | 5 | 826516 | 5 | 3981 | [S |
TRACE 3981:
java/awt/image/DataBufferUShort.<init>(
DataBufferUShort.java:50)
java/awt/image/Raster.createPackedRaster(
Raster.java:400)
java/awt/image/DirectColorModel.
createCompatibleWritableRaster(
DirectColorModel.java:641)
sun/awt/windows/WComponentPeer.createImage(
WComponentPeer.java:186)
El código TableExample3 selecciona un scrollpane de 700 por 300. Cuando miramos el fuente de Raster.java, qu está en el fichero src.jar, encontraremos estas sentencias en la línea 400:
case DataBuffer.TYPE_USHORT:
d = new DataBufferUShort(w*h);
break;
Los valores w y h son la anchura y altura de la llamada a createImage que arranca en TRACE 3981. El constructor DataBufferUShort crea un array de shorts:
donde size es w*h. Por eso, en teoría debería hacer una entrada en el array para 210000 elementos. Buscamos una enteada por cada ejemplarización de esta clase buscando por trace=3981. Una de las cinco entradas se parecerá a esto:data = new short[size];
OBJ 5ca1fc0 (sz=28, trace=3979,
class=java/awt/image/DataBufferUShort@9a2570)
data 5ca1670
bankdata 5ca1f90
offsets 5ca1340
ARR 5ca1340 (sz=4, trace=3980, nelems=1,
elem type=int)
ARR 5ca1670 (sz=420004, trace=3981, nelems=210000,
elem type=short)
ARR 5ca1f90 (sz=12, trace=3982, nelems=1,
elem type=[S@9a2d90)
[0] 5ca1670
Podemos ver que los valores de los datos de estas referencias de imagen en un array 5ca1670 que devuelve un alista de 210000 elementos short de tamaño 2. Esto significa qu este array usa 420004 bytes de memoria.
De este dato podemos concluir que el programa TableExample3 usa cerca de 0.5Mb para mapear cada tabal. Si la aplicación de ejemplo se ejecuta en una máquina con poca memoria, debemos asegurarnos de que no mantenemos referencias a objetos geandes o a imágenes que fueron construidas con el método createImage.
java -Xrunhprof:file=TableExample3.hprof,format=b
TableExample3
Para generar el informe binario, cerramos la ventana TableExample3. El fichero de informe binario TableExample3.hprof se crea al salir del programa. La Herramienta de Análisis de Heap arranca un servidor HTTP que analiza el fichero de perfil binario y muestra el resultado en un HTML que podemos ver en un navegador.
Podemos obtener una copia de la Herramienta de Análisis de Heap de la site java.sun.com. Una vez instalado, ejecutamos los scripts shell y batch en el directorio bin instalado para poder ejecutar el servidor de la Herramienta de Análisis de Heap de esta forma:
>hat TableExample3.hprof
Started HCODEP server on port 7000
Reading from /tmp/TableExample3.hprof...
Dump file created Tue Jan 05 13:28:59 PST 1999
Snapshot read, resolving...
Resolving 17854 objects...
Chasing references,
expect 35 dots.......................
Eliminating duplicate
references.........................
Snapshot resolved.
Server is ready.
La salida de arriba nos dice que nuestro servidor HTTP se ha arrancado en el puerto 7000. Para ver este informe introducimos la URL http://localhost:7000 o http://your_machine_name:7000 en nuestro navegador Web. Si tenemos problema en arrancar el servidor usando el script, podemos alternativamente ejecutar la aplicación incluyendo el fichero de clases hat.zip en nuestro CLASSPATH y usar el siguiente comando:
La vista del informe por defecto contiene una lista de todas las clases. En la parte de abajo de está página inicial están las dos opciones básicas de informes:java hat.Main TableExample3.hprof
Si seleccionamos el enlace Show all members of the rootset, veremos un alista de las siguientes referencias porque estas referencias apuntan a picos potenciales de memoria.Show all members of the rootset Show instance counts for all classes
Lo que vemos aquí son ejemplares en la aplicación que tienen referencias a objetos que tienen un riesgo de no se recolectados para la basura. Esto puede ocurrir algunas veces en el caso del JNI su se asigna memoria para un objeto, la memoria se deja para que la libere el recolector de basura, y el recolector de basura no teine la información que necesita para hacerlo. En esta lista de referencias, estamos principalmente interesados en un gran número de referencias a objetos o a objetos de gran tamaño.Java Static References Busy Monitor References JNI Global References JNI Local References System Class References
El otro informe clave es el Show instance counts for all classes. Este lista los números de llamadas a un método particular. Los objetos array String y Character, [S y [C, están siempre en la parte superior de esta lista, pero algunos objetos son un poco más intrigantes. ¿Por qué hay 323 ejemplares de java.util.SimpleTimeZone, por ejemplo?
5109 instances of class java.lang.String
5095 instances of class [C
2210 instances of class java.util.Hashtable$Entry
968 instances of class java.lang.Class
407 instances of class [Ljava.lang.String;
323 instances of class java.util.SimpleTimeZone
305 instances of class
sun.java2d.loops.GraphicsPrimitiveProxy
304 instances of class java.util.HashMap$Entry
269 instances of class [I
182 instances of class [Ljava.util.Hashtable$Entry;
170 instances of class java.util.Hashtable
138 instances of class java.util.jar.Attributes$Name
131 instances of class java.util.HashMap
131 instances of class [Ljava.util.HashMap$Entry;
130 instances of class [Ljava.lang.Object;
105 instances of class java.util.jar.Attributes
Para obtener más información sobre los ejemplares SimpleTimeZone, pulsamos sobre el enlace (la línea que empieza por 323). Esto listará las 323 referencias y calculará cuánta memoria ha sido utilizada. en este ejemplo, se han utilizado 21964 bytes.
Si pulsamos sobre uno de estos ejemplares SimpleTimeZone, veremos donde fue asignado este objeto.Instances of java.util.SimpleTimeZone class java.util.SimpleTimeZone java.util.SimpleTimeZone@0x004f48c0 (68 bytes) java.util.SimpleTimeZone@0x003d5ad8 (68 bytes) java.util.SimpleTimeZone@0x004fae88 (68 bytes) ..... Total of 323 instances occupying 21964 bytes.
Object allocated from:
java.util.TimeZoneData.<clinit>(()V) :
TimeZone.java line 1222
java.util.TimeZone.getTimeZone((Ljava/lang/String;)
Ljava/util/TimeZone;) :
TimeZone.java line (compiled method)
java.util.TimeZone.getDefault(
()Ljava/util/TimeZone;) :
TimeZone.java line (compiled method)
java.text.SimpleDateFormat.initialize(
(Ljava/util/Locale;)V) :
SimpleDateFormat.java line (compiled method)
En este ejemplo el objeto fue asignado desde TimeZone.java. El fichero fuente de este fichero están el fichero estándard src.jar, y examinando este fichero, podemos ver que de hehco hay cerca de 300 de estos objetos en memoria.
static SimpleTimeZone zones[] = {
// The following data is current as of 1998.
// Total Unix zones: 343
// Total Java zones: 289
// Not all Unix zones become Java zones due to
// duplication and overlap.
//-------------------------------------------
new SimpleTimeZone(-11*ONE_HOUR,
"Pacific/Niue" /*NUT*/),
Desafortunadamente, no tenemos control sobre la memoria usada en este ejemplo, porque es asignada cuando el programa hizo la primera solicitud al timezone por defecto. Sin embargo, esta misma técnica puede aplicarse para analizar nuestra propia aplicación donde probablemente podríamos hacer algunas mejoras.
Podemos usar una o dos opciones de perfil de CPU para conseguir esto. La primera opción es cpu=samples. Esta opción devuelve el resultado de un muestreo de ejecución de threads de la Máquina Virtual Java con un conteo estadístico de la frecuencia de ocurrencia con que se usa un método particular para encontrar secciones ocupadas de la aplicación. La segunda opción es cpu=times, que mide el tiempo que tardan los métodos individuales y genera un ranking del porcentaje total del tiempo de CPU ocupado por la aplicación.
Usando la opción cpu=times, deberíamos ver algo como esto al final del fichero de salida:
CPU TIME (ms) BEGIN (total = 11080)
Fri Jan 8 16:40:59 1999
rank self accum count trace method
1 13.81% 13.81% 1 437 sun/
awt/X11GraphicsEnvironment.initDisplay
2 2.35% 16.16% 4 456 java/
lang/ClassLoader$NativeLibrary.load
3 0.99% 17.15% 46 401 java/
lang/ClassLoader.findBootstrapClass
Si constrastamos esto con la salida de cpu=samples, veremos la diferencia entre la frecuencia de ejecuciónde un método durante la ejecución de la aplicación comparada con el tiempo que tarda ese método.
CPU SAMPLES BEGIN (total = 14520)
Sat Jan 09 17:14:47 1999
rank self accum count trace method
1 2.93% 2.93% 425 2532 sun/
awt/windows/WGraphics.W32LockViewResources
2 1.63% 4.56% 237 763 sun/
awt/windows/WToolkit.eventLoop
3 1.35% 5.91% 196 1347 java/
text/DecimalFormat.<init>
El método W32LockView, que llama a una rutina de bloqueo de ventana nativa, se llama 425 veces. Por eso cuando aparecen en los threads activos porque también toman tiempo para completarse. En contraste, el método initDisplay sólo se le llama una vez, pero es el método que tarda más tiempo en completarse en tiempo real.
Aquí hay una lista de herramietnas que podemos usar para analizar problemas de rendimiento en algunos sistemas operativos más comunies.
developer$ sar 1 10
SunOS developer 5.6 Generic_105181-09 sun4u
02/05/99
11:20:29 %usr %sys %wio %idle
11:20:30 30 6 9 55
11:20:31 27 0 3 70
11:20:32 25 1 1 73
11:20:33 25 1 0 74
11:20:34 27 0 1 72
El comando truss sigue y guarda los detalles de cada llamada al sistema por la JVM al kernel Solaris. Un forma común de usar truss es:
El parámetro -f sigue cualquier proceso hijo que haya creado, el parámetro -o escribe la salida en el fichero nombrado, y el parámetro -p sigue un programa en ejecución desde sis ID de proceso. De forma alternativa podemos reemplazar -p <process id> con la JVM, por ejemplo:truss -f -o /tmp/output -p <process id>
El /tmp/output es usado para almacenar la salida de truss, lo que se debería parecer a esto:truss -f -o /tmp/output java MyDaemon
15573: execve("/usr/local/java/jdk1.2/solaris/
bin/java", 0xEFFFF2DC,
0xEFFFF2E8) argc = 4
15573: open("/dev/zero", O_RDONLY) = 3
15573: mmap(0x00000000, 8192,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE, 3, 0) = 0xEF7C0000
15573: open("/home/calvin/java/native4/libsocket.so.1",
O_RDONLY) Err#2 ENOENT
15573: open("/usr/lib/libsocket.so.1",
O_RDONLY) = 4
15573: fstat(4, 0xEFFFEF6C) = 0
15573: mmap(0x00000000, 8192, PROT_READ|PROT_EXEC,
MAP_SHARED, 4, 0) = 0xEF7B00 00
15573: mmap(0x00000000, 122880, PROT_READ|PROT_EXEC,
MAP_PRIVATE, 4, 0) = 0xEF7 80000
15573: munmap(0xEF78E000, 57344) = 0
15573: mmap(0xEF79C000, 5393,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_FIXED, 4, 49152)
= 0xEF79C000
15573: close(4) = 0
En la salida de truss, buscamos los ficheros que fallaran al abrirlos debido a problemas de acceso, como un error ENOPERM, o un error de fichero desaparecido ENOENT. También podemos seguir los datos leidos o escrito con los parámetros de truss: -rall para seguir todos los datos leídos, o -wall para seguir todos los datos escritos por el programa. Con estos parámetros, es posible analizar datos enviados a través de la red o a un disco local.
$ strace -f -o /tmp/output
java sun.applet.AppletViewer
example1.html
$ cat /tmp/output
639 execve("/root/java/jdk117_v1at/java/
jdk117_v1a/bin/java", ["java",
"sun.applet.AppletViewer ",
"example1.html"], [/* 21 vars */]) = 0
639 brk(0) = 0x809355c
639 open("/etc/ld.so.preload", O_RDONLY) = -1
ENOENT (No such file or directory)
639 open("/etc/ld.so.cache", O_RDONLY) = 4
639 fstat(4, {st_mode=0, st_size=0, ...}) = 0
639 mmap(0, 14773, PROT_READ, MAP_PRIVATE,
4, 0) = 0x4000b000
639 close(4) = 0
639 open("/lib/libtermcap.so.2", O_RDONLY) = 4
639 mmap(0, 4096, PROT_READ, MAP_PRIVATE,
4, 0) = 0x4000f000
Para obtener información del sistema similar al comando sar de Solaris, lee los contenidos del fichero /proc/stat. El formato de este fichero se describe en las páginas del manual proc. Miramos la línea cpu para obtener la hora del sistema de usuario:
En el ejemplo anterior, la salida cpu indica 48.27 segundos de espacio de usuario, 0.04 de prioridad máxima, 16.36 segundos procesando llamadas al sistema, y 168 segundos libre. Esta es una ejecución total, las entradas para cada proceso están disponibles en /proc/<process_id>/stat.cpu 4827 4 1636 168329
Análisis de memoria: Memory meter
Análisis de Red: Traceplus