Escribir Aplicaciones Avanzadas para la Plataforma Java 2

Una ley no escrita de la programaci�n sentencia que gastatemos el 10 por cien de nuestro tiempo en el primer 90 por ciento de un proyecto, y el otro 90 por ciento de nuestro tiempo en el 10 por cierto restante. Esto suena igual que cualquiera de nuestros proyectos, probablemente estamosgastando el �ltimo 10 por ciento en depuraci�n e integraci�n. Mientras que hay cantidad de libros y de gente ayud�ndonos a empezar un progyecto, hay muy pocos recursor disponibles para ayudarnos a finalizarlo.

La buena noticia es que este cap�tulo se enfoca completamente en la depuraci�n, y en evitar que nuestro proyecto se pase de tiempo. Usa ejemplos del mundo real para pasear a trav�s de pasos sencillos para depurar y fijar nuestros programas. Cuando terminemos, deberemos ser unos expertos en el seguimiento de problemas en programas escritos en Java -- applets, aplicaciones y servlets -- de todas las formas y tama�os.

.�Recolecci�n de Evidencias

El primer paso para intentar resolver cualquier problema es obtener tanta informaci�n como sea posible. Si podemos imagninarnos la escena de un crimen,sabemos que todo est� chequeado, catalogado y analizado antes de alcanzar cualquier conclusi�n. Cuando se depura un programa, no tenemos armas, muestras de pelo, ni huellas dactilares, pero existen cantidad de evidencias que podemos obtener y que podr�an contener o apuntar a la soluci�n �ltima. Esta secci�n expl�ca como recoger esas evidencias.

.�Instalaci�n y Entorno

La plataforma Java es una tecnolog�a cambiante y de r�pido movimiento. Podr�amos tener m�s de una versi�n instalada en nuestro sistema, y esas versiones podr�an haber sido instaladas como parte de la instalaci�n de otros productos. En un entorno con versiones mezcladas, un programa puede experimentar problemas debido a los cambios de la plataforma en las nuevas versiones.

Por ejemplo, si las clases, las librer�as, o las entradas de registro de Window de instalaciones anteriores permanecen en nuenstro sistema despu�s de una actualizaci�n, hay una oportunidad de que la mezcla del nuevl software sea la causante de nuestros problemas y necesita ser investigada y eliminada. Las oportunidades para los problemas relacionados con la mezcla de versiones de software se ha incrementado con el uso de diferentes versiones de herramientas para desarrollar software de la plataforma Java.

La secci�n sobre Problemas con Versiones al final de este cap�tulo proporciona una lista completa de las principales versiones de la plataforma Java para ayudarnos a resolver nuestros problemas con versiones de software.

.�Path de Clases

En la plataforma Java 2, la variable de entorno CLASSPATH es necesaria para especificar a la propia aplicaci�n d�nde est�n sus clases, y no las clases de la plataforma Java como en versiones anteriores. Por eso es posible que nuestro CLASSPATH apunte a las clases de la plataforma Java desde versiones anteriores y nos cause problemas.

Para examinar el CLASSPATH, tecleamos esto en la l�nea de comando:

Windows 95/98/NT: echo %CLASSPATH%

Unix: echo $CLASSPATH

Las clases Java se cargan en primer lugar, primera forma b�sica de la lista CLASSPATH. Si la variable CLASSPATH contiene una referencia a un fichero lib/classes.zip, que apunta a una instalaci�n diferente de la plataforma Java, esto peude causar que se cargen clases incomplatibles.

Nota: En la plataforma Java 2, las clases del sistema se eligen antes de cualquier clases de la lista CLASSPATH para minimizar de que se caeguen clases Java anteriores a la clase Java 2 del mismo nombre.

La variable CLASSPATH puede obtener su configuraci�n desde la l�nea de comandos o desde las selecciones de configuraci�n como aquellas especificadas en el Entorno de Usuario sobre Windows NT, un fichero autoexec.bat, o un fichero de arranque del shell .cshrc sobre Unix.

Podemos controlar las clases de la M�quina Virtual Java usadas para compilar nuestros programas con una opci�n especial de la l�nea de comandos que nos permite suministrar el CLASSPATH que querramos. La opci�n y par�metro de la plataforma Java 2 -Xbootclasspath classpath, y las versiones anteriores usan -classpath classpath y -sysclasspath classpath. Sin importar la versi�n que estamos ejecutando, el par�metro classpath especifica el classpath del sistema y del usuario, y los ficheros zip o JAR a usar en la compilaci�n.

Para compilar y ejecutar el programa Myapp.java con un CLASSPATH suministrado en la l�nea de comandos, usamos las siguientes instrucciones:

Windows 95/98/NT:

En este ejemplo, la plataforma Java est� instalada en el directorio C:\java. Tecleamos los siguiente en una s�la l�nea:

javac -J-Xbootclasspath:c\java\lib\tools.jar;c:
\java\jre\lib\rt.jar;c:\java\jre\lib\i18n.jar;.  
  Myapp.java

No necesitamos la bandera -J para ejecutar el programa Myapp compilado, s�lo tecleamos esto en una s�la l�nea:

java -Xbootclasspath:c:\java\jre\lib\rt.jar;c:
\java\jre\lib\i18n.jar;.  Myapp

Sistemas Unix:

En este ejemplo, la plataforma Java est� instalada en el directorio /usr/local/java. Tecleamos todo en una s�la l�nea:

javac -J-Xbootclasspath:/usr/local/java/lib/tools.jar:
/usr/local/java/jre/lib/rt.jar:
/usr/local/java/jre/lib/i18n.jar:. Myapp.java

No necesitamos la bandera -J para ejecutar el programa Myapp compilado, s�lo tecleamos esto en un s�la l�nea:

java -Xbootclasspath:/usr/local/java/jre/lib/rt.jar:
/usr/local/java/jre/lib/i18n.jar:. Myapp

.�Carga de Clases

Otra forma de analizar problemas con el CLASSPATH es localizar desde d�nde est� cargando las clases nuestra aplicaci�n. La opci�n -verbose del comando java muestra de donde vienen los ficheros .zip o .jar cuando se carga. De esta forma, podremos decir si vienen del fichero zip de la plataforma Java o desde alg�n fichero JAR de la aplicaci�n.

Por ejemplo, una aplicaci�n podr�a estar usando la clase Password que escribimos para ella o podr�a estar cargando la clase Password desde la herramienta IDE instalado.

Deber�asmos ver cada nombre de fichero zip o Jar como se v� aqu�:

$ java -verbose SalesReport
[Opened /usr/local/java/jdk1.2/solaris/jre/lib/rt.jar 
        in 498 ms]
[Opened /usr/local/java/jdk1.2/solaris/jre/lib/i18n.jar 
        in 60 ms]
[Loaded java.lang.NoClassDefFoundError from 
	/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]
[Loaded java.lang.Class from 
	/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]
[Loaded java.lang.Object from 
	/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]

.�Incluir C�digo de Depurado

Una forma com�n de a�adir c�digo de diagn�stico a una aplicaci�n es usa sentencias System.out.println en posiciones estrat�gicas de la aplicaci�n. Esta t�cnica est� bien durante el desarrollo, pero debemos acordarnos de eliminarlas todas antes de liberar nuestro producto. Sin embargo hay otras aproximaciones que son tan sencillas y que no afectan al rendimiento de nuestra aplicaci�n, y no muestra mensajes que no queremos que vea el cliente.

.�Activar la Informaci�n de Depuraci�n en Tiempo de Ejecuci�n

La primera alternativa a las cl�sicas sentencias de depuraci�n println es activar la informaci�n de depuraci�n en el momento de la ejecuci�n. Una ventaja de esto es que no necesitamos recompilar ning�n c�digo si aparecen problemas mientras hacemos pruebase en la oficina del cliente.

Otra ventaja es que algunas veces los problemas de software pueden ser atribuidos a condiciones de carrera donde el mismo segmento de c�digo se convierte en impredecible debido al tiempo entre cada iteracci�n del programa. Si controlamos el c�digo de operaci�n desde la l�nea de comandos en lugar de a�adir sentencias de depuraci�n println, podemos arreglar la secuencia de problemas que causa las condiciones de carrera que vienen desde el c�digo println. Esta t�cnica tambi�n nos evita tener que a�adir y eliminar las sentencias println y tener que recompilar nuestro c�digo.

Esta t�cnica requiere que usemos una propiedad del sistema como bandera de depurado y que incluyamos c�digo en la aplicaci�n para comprobar que el valor de esta propiedad del sistema. Para activar la informaci�n de depuraci�n desde la l�nea de comandos en el momento de la ejecuci�n, arrancamos la aplicaci�n y seleccionamos la propiedad del sistema debug a true de esta forma:

java -Ddebug=true TestRuntime

El c�digo fuente que necesita la clase TestRuntime para examinar esta propiedad y configurar la bandera booleana debug de es el siguiente:

public class TestRuntime {
    boolean debugmode; //global flag that we test

    public TestRuntime () {

       String dprop=System.getProperty("debug");

       if ((dprop !=null) && (dprop.equals("yes"))){
            debugmode=true;
       }

       if (debugmode) {
           System.err.println("debug mode!");
       }
    }
}

.�Crear Versiones de Depuraci�n y Producci�n en Tiempo de Compilaci�n

Como se mencion� antes, un problem con la adici�n de sentencias System.out.println para depurar nuesto c�digo es que debemos eliminarlas antes de liberar nuestro producto. Adem�s de a�adir c�digo innecesario, las sentecias de depuraci�n println pueden contener informaci�n que no queremos que vea el cliente.

Una forma de eliminar las sentencias de depuraci�n System.out.println de nuestro c�digo es usar la siguiente optimizaci�n del compilador para eleminar los corchetes pre-determinados de nuestos c�digo en el momento de la compilazi�n y activar alguna algo similar a un depurador pre-procesador.

Este ejemplo usa una bandera booleana est�tica dmode que cuando se selecciona a false resulta en la eliminaci�n el c�digo de depuraci�n y de las sentencias de depuraci�n. Cuando el valor de dmode se selecciona a true, el c�digo es incluido en el fichero class compilado y est� disponible en la aplicaci�n para prop�sitos de depuraci�n.

class Debug {

  //set dmode to false to compile out debug code
  public static final boolean dmode=true;
}

public class TestCompiletime {

  if (Debug.dmode) {                       // These 
    System.err.println("Debug message");   // are 
    }                                      // removed
}

.�Usar M�todos de Diagn�sticos

Podemos usar m�todos de diagn�stico para solicitar informaci�n de depuraci�n desde la m�quina virtual Java (JVM). Los dos siguientes m�todos de la clase Runtime siguel las llamadas a m�todos y los bytes codes de la JVM que usa nuestra aplicaci�n. Como estos dos m�todos producen cantidad de informaci�ne s mejor seguir peque�as cantidades de c�digo, incluso tan peque�as como una l�nea a la vez.

Para permitie seguir las llamadas, tenemos que arrancan la JVM con los comandos del int�rprete java_g o java -Xdebug.

Para listar cada m�todo cuando es invocado durante la ejecuci�n, a�adimos la siguiente l�nea antes del c�digo donde queremos empezar a seguir la pista y a�adimos la correspondiente l�nea traceMethodCalls con el argumento seleccionado a false para desactivar el seguimiento. La informaci�n de seguimiento se muestra en la salida est�ndard.

// set boolean argument to false to disable
Runtime.getRuntime().traceMethodCalls(true);
callMyCode();
Runtime.getRuntime().traceMethodCalls(false);

Para ver cada l�nea en bytecodes cuando se ejecutan, a�adimos la siguiente l�nea al c�digo de nuestra aplicaci�n:

// set boolean argument to false to disable
Runtime.getRuntime().traceInstructions(true);
callMyCode();
Runtime.getRuntime().traceInstructions(false);

Tambi�n podemos a�adir la siguiente l�nea para que nuestra aplicaci�n vuelque la pila usando el m�todo dumpStack de la clase Thread. La salida de este volcado de pila se explica en An�lisis y Seguimiento de la Pila, pero ahora podemos pensar en la pila como un apunte de los threads que se est�n ejecutando en la JVM.

Thread.currentThread().dumpStack();

.�A�adir Informaci�n de Depurado

La informaci�n de variables locales no est� incluida en el coraz�n de las clases del sistema de la plataforma Java. Por eso, si usamos una herramienta de depuraci�n para listar variables lcoales para clases del sistema donde coloquemos comandos stop , obtendremos la siguiente salida, incluso cuando compilemos con la bandera -g como sugiere la salida. Esta salida es de una sesi�n jdb:

main[1] locals
No local variables: try compiling with -g

Para obtener acceso a la informaci�n de variables lcoales, tenemos que obtener el fuente (src.zip o src.jar) y recompilarlo con una bandera debug. Podemos obtener el fuente de la mayor�a de las clases java.* classes con la descarga de los binarios desde java.sun.com.

Una vez hayamos descargado el fichero src.zip o src.jar, extraemos s�lo los ficheros que necesitamos. Por ejemplo, para extraer la clase String, tecleamos esto en la l�nea de comandos:

unzip /tmp/src.zip src/java/lang/String.java

o

jar -xf /tmp/src.jar src/java/lang/String.java

Recompilamos la clase o clases extraidas con la opci�n -g. Tambi�n podemos a�adir nuestros propios diagn�sticos adicionales sobre el fichero fuente en este momento.

javac -g src/java/lang/String.java

El compilador Java 2 javac ofrece m�s opciones que s�lo la opci�n original -g para c�digo de depuraci�n, y podemos reducir el tama�o de nuestras clases usando -g:none, que nos ofrece una reducci�n de un 10% del tama�o.

Para ejecutar la aplicaci�n con las nuevas clases compiladas, necesitamos usar la opcio�n bootclasspath para que esas clases se utilicen en primer lugar.

Tecleamos lo siguiente en una s�la l�nea con espacio antes de myapp.

Plataforma Java 2 Win95/NT:

Este ejemplo asume que la plataforma Java est� instalada en c:\java, y los ficheros fuente est�n en c:\java\src:

jdb -Xbootclasspath:c:\java\src;c:\java\jre\lib\rt.jar;c:
\java\jre\i18n.jar;.  myapp

Sistemas Unix:

Este ejemplo asume que la plataforma Java est� instalada en /usr/local/java, y los ficheros fuente est�n en /usr/local/java/src.

jdb -Xbootclasspath:/usr/java/src;
/usr/java/jre/lib/rt.jar;
/usr/java/jre/i18n.jar;.  myapp

La siguiente vez que ejecutemos el comando locals veremos los campos internos de la clase que deseamos analizar.

.�Ejecutar Tests y Analizar

Si todav�a tenemos problemas incluso despu�s de haber revisado los problemas de instalaci�n y de entorno y haber incluido c�digo de depuraci�n, es el momento de usar herramientas para probar y analizar nuestro programa.

.�Trabajar Detr�s de la Silla con jdb

Aunque hay algunas muy buenas herramientas IDE en el mercado, la herramienta de depuraci�n Java, jdb y sus sucesores tienen un papel importante que jugar en la prueba y depuraci�n de programa. algunas ventajas de jdb sobre los IDE es que es gratis, es independiente de la plataforma (algunos IDE no lo son), y se ejecuta como un proceso separado al programa que est� depurando. El beneficio de ejecutar jdb como un proceso separado es que podemos a�adir una sesi�n de depurado a un programa que est� ejecut�ndose.

El lado negativo de usar jdb es que s�lo hay un interface de l�nea de comandos, y trata con el mismo c�digo que est�mos tratando de depurar. Esto significa que si hay un bug enla m�quina virtual Java, jdb se podr�a equivocar al intentar diagnisticar el mismo bug!

La nueva arquitectura JBUG se cre� para resolver estos problemas en el jdb. JBUG, entre otras cosas, proporciona un API de ayuda de depuraci�n en la m�quina virtual Java llamado "Java VM Debug Interface" (JVMDI). Este ayudante se comunica con el depurador desde el final usando el "Java Debug Wire Protocol" (JDWP). La depuraci�n desde el final usa el interface remoto "Java Debug Interface" (JDI) para enviar y recibir comando sobre el protocolo JDWP. JBug est� disponible para la plataforma Java 2, y tiene un estilo jdb que aprenderemos m�s adelante.

.�Prueba Sencilla con jdb

De vuelta a la cl�sica herramienta jdb. Aqu� tenemos uno sencillos pasos para analizar un programa usando jdb. Este primer ejemplo depura un programa de la aplicaci�n startup. El ejemplo Depuraci�n Remota muestra como conectarlo con una aplicaci�n que se est� ejecutando.

.�Arrancar la Sesi�n

Para empezar una sesi�n de depurado, compilamos el programa SimpleJdbTest.java con informaci�n completa de depurado usando javac y la bandera -g. En este ejemplo, el programa SimpleJdbTest.java es una aplicaci�n pero tambi�n podr�a ser un applet. Los procedimientos para depurar aplicaciones son iguales que para depurar applets una que se ha empezado la sesi�n de depurado.

javac -g SimpleJdbTest.java

Luego arrancamos la herramienta jdb con el nombre de la clase del programa como par�metro:

jdb SimpleJdbTest
Initializing jdb...
0xad:class(SimpleJdbTest)

Para depurar un applet en el appletviewer usamos el par�metro -debug como en este ejemplo:

$ appletviewer -debug MyApplet.html
Initializing jdb...
0xee2f9808:class(sun.applet.AppletViewer)
> 

.�Seleccionar un m�todo de ruptura y m�todos de listado

En este punto, s�lo se ha cargado la clase SimpleJdbTest; no se ha llamado al constructor de la clase. Para hacer que el jdb se pare cuando el programa se inicializa por primera vez, ponemos un stop, o punto de ruptura, en el constructor usando el comando stop in. Cuando se seleccionan puntos de ruptura, instuirmos al jdb a ejecutar nuestro programa usando el comando run de esta forma:

stop in SimpleJdbTest.<init>
Breakpoint set in SimpleJdbTest.<init>
run
run SimpleJdbTest
running ...
main[1]
Breakpoint hit: SimpleJdbTest.<init> 
                  (SimpleJdbTest:10)

La herramienta jdb se para en la primera l�nea del constructor. Para listar los m�todo que fueron llamados hasta llegar a este punto de ruptura, introducimos el comando where:

main[1] where
[1] SimpleJdbTest.<init> (SimpleJdbTest:10)
[2] SimpleJdbTest.main (SimpleJdbTest:29)

Los m�todos numerados de la lista es el �ltimo marco de pila que ha alcanzado la JVM. En este caso el �ltimo marco de pula es el constructor SimpleJdbTest que fue llamado desde el SimpleJdbTest main.

Siempre que se llama a un nuevo m�todo, se sit�a en esta lista de pila. La tecnolog�a Hotspot consigue alguna de sus ganancias de velocidad elimando un nuevo marco de pila cuando se llama a un nuevo m�todo. Para obtener una apreciaci�n general de d�nde se par� el c�digo, introducimos el comando list.

main[1] list
6 		Panel p;
7 		Button b;
8 		int counter=0;
9
10 		SimpleJdbTest() {
11 			setSize(100,200);
12 			setup();
13 		}
14 		void setup (){

.�Localizar la Fuente

Si el fuente del fichero class parado no est� disponible en el path actual, podemos decirle a jdb donde encontrar el fuente con el comando use d�ndole el directorio fuente como un par�metro. En el siguiente ejemplo el fuente est� un subdirectorio o carpeta llamado book.

main[1] list
Unable to find SimpleJdbTest.java
main[1] use book
main[1] list
6 		Panel p;
7 		Button b[];
8 		int counter=0;
9 
10 	=> 	SimpleJdbTest() {

.�Buscar un M�todo

Para ver que sucede en el m�todo setup de SimpleJdbText, usamos el comando step para pasar a trav�s de sus 4 l�neas y ver lo que pasa.

main[1] step
main[1]
Breakpoint hit: java.awt.Frame.<init> (Frame:222)

Pero espera un minuto! Este es ahora el constructor de la clase Frame! Si lo seguimos pasaremos a trav�s del constructor de la clase Frame y no el de la clase SimpleJdbText. Porque SimpleJdbTest desciende de la clase Frame, el constructor padre, que en este caso es Frame, es llamado sin avisarnos.

.�El comando step up

Podr�amos continuar pasando y eventualmente volver�amos al constructor de SimpleJdbTest, pero para retornar inmediatamente podemos usar el comando step up para volver al constructor de SimpleJdbTest.

main[1] step up
main[1]
Breakpoint hit: SimpleJdbTest.<init> 
                  (SimpleJdbTest:8)

.�El comando next

Tambi�n podemos usar el comando next para obtener el m�todo setup. En este siguiente ejemplo, la herramienta jdb ha aproximado que el fichero fuente est� fuera del constructor cuando proces� el �ltimo comando step up. Para volver al constructor, usamos otro comando step, y para obtener el m�todo setup, usamos un comando next. Para depurar el m�todo setup, podemos step (pasar) a trav�s del m�todo setup.

main[1] step
Breakpoint hit: SimpleJdbTest.<init> 
                  (SimpleJdbTest:11)
main[1] list
7 		Button b[]=new Button[2];
8		int counter=0;
9
10 		SimpleJdbTest() {
11 			setSize(100,200);<
12 			setup();
13 		}
14 		void setup (){
15 			p=new Panel();
16		}
main[1] next
Breakpoint hit: SimpleJdbTest.<init> 
                  (SimpleJdbTest:12)
main[1] step
Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:15)

.�El comando stop in

Otra forma de obtener el m�todo setup es usar el comando stop in SimpleJdbTest.setup. Podemos listar el fuente de nuevo para saber donde estamos:

main[1] list
11 			setSize(100,200);
12 			setup();
13 		}
14 		void setup (){
15	=>		p=new Panel();
16 			  b[0]= new Button("press");
17 			  p.add(b[0]);
18 			  add(p);
19

.�El comando print

Lo primero que hace el m�todo setup es crear un Panel p. Si intentamos mostrar el valor de p con el comando print p, veremos que este valor es null.

main[1] print p
p = null

Esto ocurre porque la l�nea a�n no se ha ejecutado y por lo tanto al campo p no se le ha asignado ning�n valor. Necesitamos pasar sobre la sentencia de asignaci�n con el comando next y luego usar de nuevo el comando print p.

main[1] next

Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:16)
main[1] print p
p = java.awt.Panel[panel0,0,0,0x0,invalid,
		     layout=java.awt.FlowLayout]

.�Seleccionar Puntos de Ruptura en M�todos Sobrecargado

Aunque pasar a trav�s de clases peque�as es r�pido, como regla general en grandes aplicaciones, es m�s r�pido usar puntos de ruptura. Esto es as� porque jdb tiene un conjunto de comandos muy simples y no teine atajos, por eso cada comando teine que ser pegado o tecleado por completo.

Para seleccionar un punto de ruptura en la clase Button, usamos stop in java.awt.Button.<init>

main[1] stop in java.awt.Button.<init>
java.awt.Button.<init> is overloaded,
          use one of the following:
void <init>
void <init>java.lang.String)

El mensaje explica porque jdb no puede parar en este m�todo sin m�s informaci�n, pero el mensaje nos explica que s�lo necesitamos ser expl�citos en el tipo de retorno para los m�todos sobrecargados en los que queremos parar. Para parar en el constructor de Button que crea este Button, usamos stop in java.awt.Button.<init>java.lang.String).

.�El comando cont

Para continuar la sesi�n jdb, usamos el comando cont . La siguiente vez que el programa cree un Button con un constructor String, jdb se parar� para que podamos examinar la salida.

main[1] cont
main[1]
Breakpoint hit: java.awt.Button.<init> 
                          (Button:130)

Si la clase Button no ha sido compilada con informaci�n de depurado como se describi� antes, no veremos los campos internos desde el comando print.

.�Limpiar Puntos de Ruptura

Para limpiar este punto de ruptura y que no pare cada vez que se cree un Button se usa el comando clear. Este ejemplo usa el comando clear sin argumentos para mostrar la lista de puntos de ruptura actuales, y el comando clear con el argumento java.awt.Button:130. para borrar el punto de ruptura java.awt.Button:130..

main[1] clear
Current breakpoints set:
SimpleJdbTest:10
java.awt.Button:130
main[1] clear java.awt.Button:130
Breakpoint cleared at java.awt.Button: 130

.�Mostrar Detalles del Objeto

Para mostrar los detalles de un objeto, usamos el comando print para llamar al m�todo toString del objeto, o usar el comando dump para mostrar los campos y valores del objeto.

Este ejemplo pone un punto de ruptura en la l�nea 17 y usa los comandos print y dump para imprimir y volcar el primer objeto Button del array de objetos Button. La salica del comando The dump ha sido abreviada.

main[1] stop at SimpleJdbTest:17
Breakpoint set at SimpleJdbTest:17
main[1] cont
main[1]
Breakpoint hit: SimpleJdbTest.setup (SimpleJdbTest:17)

main[1] print b[0]
b[0] = java.awt.Button[button1,0,0,0x0,invalid,
					label=press]
main[1] dump b[0]
b[0] = (java.awt.Button)0x163 {
private int componentSerializedDataVersion = 2
boolean isPacked = false
private java.beans.PropertyChangeSupport 
			changeSupport = null
long eventMask = 4096
transient java.awt.event.InputMethodListener 
			inputMethodListener = null
....
java.lang.String actionCommand = null
java.lang.String label = press
}

.�Finalizar la Sesi�n

Esto finaliza el sencillo ejemplo jdb. Para terminar una sesi�n jdb, se usa el comando quit:

0xee2f9820:class(SimpleJdbTest)
> quit

.�Depuraci�n Remota

El jdb es un proceso de depuraci�n externo, lo que significa que depura el programa envi�ndole mensajes hacia y desde el ayudante de la m�quina virtual Java. Esto hacer muy f�cil la depuraci�n de un programa en ejecuci�n, y nos ayuda a depurar un programa que interactua con el usuario final. Una sesi�n de depuraci�n remota desde la l�nea de comandos no interfiere con la operaci�n normal de la aplicaci�n.

.�Arrancar la Sesi�n

Antes de la versi�n Java 2, lo �nico que se requer�a para permitir la depuraci�n remota era arrancar el programa con la bandera -debug como primer argumento, y si la aplicaci�n usa librer�as nativas, terminanos el nombre de la librer�a con una _g. Por ejemplo, necesitar�amos una copia de la librer�a nativelib.dll como nativelib_g.dll para depurar con esta librer�a.

En Java 2, las cosas son un poco m�s complicada. Necesitamos decirla a la JVM d�nde est� el ficheo tools.jar usando la variable CLASSPATH. El fichero tools.jar normalmente se encuentra en el directorio lib de la instalaci�n de la plataforma Java.

Tambi�n necesitamos desactivar el compilador "Just In Time" (JIT) si existe. Este compilador se desactiva seleccionado la propiedad java.compiler a NONE o a una cadena vac�a. Finalmente, como la opci�n -classpath sobreescribe cualquier classpath seleccionado por el usuario, tambi�n necesitamos a�adir el CLASSPATH necesario para nuestra aplicaci�n.

Poni�ndo todo esto junto, aqu� est� l�nea de comandos necesaria para arrancar un programa en modo de depuraci�n remoto. Se pone todo en una s�la l�nea e incluimos todas las clases necesarias en la l�nea de comandos.

Windows:

$ java -debug -classpath C:\java\lib\tools.jar;. 
-Djava.compiler=NONE SimpleJdbTest
Agent password=4gk5hm

Unix:

$ java -debug -classpath /usr/java/lib/tools.jar:. 
-Djava.compiler=NONE SimpleJdbTest
Agent password=5ufhic

La salida es el password del agente (en este caso, 4gk5hm) si el programa se arranca de forma satisfactoria. La password de agente se suministra cuando se arranca jdb para que �ste peuda encontrar la aplicaci�n arrancada correspondiente en modo depuraci�n en esa m�quina.

Para arrancar jdb en modo depuraci�n remoto, suministramos un nombre de host, que puede ser la misma m�quina donde se est� ejecutando el programa o localhost si est�mos depurando en la misma m�quina que el programa remoto, y la password de agente.

jdb -host localhost -password 4gk5hm

.�Listar Threads

Una vez dentro de la sesi�n jdb, podemos listar los threads activos actualmente, con el comando threads, y usar el comando thread <threadnumber>, por ejemplo, thread 7 para seleccionar un thread para analizarlo. Una vez seleccionado un thread, usamos el comando where para ver los m�todos que han sido llamados por este thread.

$ jdb -host arsenal -password 5ufhic
Initializing jdb...
> threads
Group system:
1. (java.lang.Thread)0x9        Signal dispatcher  
			       cond. waiting
2. (java.lang.ref.Reference     0xb Reference Handler
      $ReferenceHandler)       cond. waiting
3. (java.lang.ref.              Finalizer
      Finalizer                cond. waiting
      $FinalizerThread)0xd            
						
4. (java.lang.Thread)0xe       Debugger agent     
		              running    
5. (sun.tools.agent.           Breakpoint handler
      Handler)0x10            cond. waiting
6. (sun.tools.agent.           Step handler
      StepHandler)0x12        cond. waiting
Group main:
7. (java.awt.                  AWT-EventQueue-0 
       EventDispatchThread)   cond. waiting 
       0x19 					         
8. (sun.awt.                    PostEventQueue-0 
      PostEventQueue)0x1b      cond. waiting          
9. (java.lang.Thread)0x1c       AWT-Motif        
			       running                
10. (java.lang.Thread)0x1d      TimerQueue       
			       cond. waiting          
11. (sun.awt.                   Screen Updater
       ScreenUpdater)0x1f      cond. waiting          
12. (java.lang.Thread)0x20      Thread-0         
			       cond. waiting          
> thread 7
AWT-EventQueue-0[1] where
  [1] java.lang.Object.wait (native method)
  [2] java.lang.Object.wait (Object:424)
  [3] java.awt.EventQueue.getNextEvent 
                            (EventQueue:179)
  [4] java.awt.EventDispatchThread.run 
	         (EventDispatchThread:67)

.�Listar el Fuente

Para listar el fuente, el thread necesita ser suspendido usando el comando suspend. Para permitir que un thread contin�e usamos el comando resume. El ejemplo usa resume 7.

AWT-EventQueue-0[1] suspend 7
AWT-EventQueue-0[1] list
Current method is native
AWT-EventQueue-0[1] where
  [1] java.lang.Object.wait (native method)
  [2] java.lang.Object.wait (Object:424)
  [3] java.awt.EventQueue.getNextEvent 
                            (EventQueue:179)
  [4] java.awt.EventDispatchThread.run 
		 (EventDispatchThread:67)
AWT-EventQueue-0[1] resume 7

.�Finalizar la Sesi�n

Cuando finalizamos de depurar remotamente este programa, eliminamos cualquier punto de ruptura restante antes de salir de la sesi�n de depuraci�n. Para obtener una lista de estos puntos de ruptura usamos el comando clear, y para eliminarlos introducimos el comando clear class:linenumber de esta forma:

main[1] clear
Current breakpoints set:
SimpleJdbTest:10

main[1] clear SimpleJdbTest:10
main[1] quit

.�Usar el Piloto Autom�tico

Un truco poco conocido del jdb es el fichero de arranque jdb. jdb autom�ticamente busca un fichero llamado jdb.ini en el directorio user.home. Si tenemos varios proyecto, es una buena idea seleccionar una propiedad user.home diferente para cada proyecto cuando arranquemos jdb. Para arrancar jdb con un fichero jdb.ini en el directorio actual, tecleamos esto:

jdb -J-Duser.home=.

El fichero jdb.ini nos permite seleccionar los comandos de configuraci�n de jdb, como use, sin tener que introducir los detalles cada vez que ejecutamos jdb. El siguiente fichero de ejemplo jdb.ini empieza una sesi�n jdb para la clase FacTest. Incluye los fuentes de la plataforma Java en el path de fuentes y le pasa el par�metro n�mero 6 al programa. Se ejecuta y para en la l�nea 13, muestra la memoria libre, y espera una entrada posterior.

load FacTest
stop at FacTest:13
use /home/calvin/java:/home/calvin/jdk/src/
run FacTest 6
memory

Aqu� est� salida de la ejecuci�n del fichero jdb.ini:

$ jdb -J-Duser.home=/home/calvin/java
Initializing jdb...
0xad:class(FacTest)
Breakpoint set at FacTest:13
running ...
Free: 662384, total: 1048568
main[1]
Breakpoint hit: FacTest.compute (FacTest:13)
main[1]

Podr�amos peguntarnos si los ficheros jdb.ini pueden usarse para controlar una sesi�n jdb completa. Desafortunadamente, los comandos en un fichero jdb.ini se ejecutan de forma s�ncrona, y jdb no espera hasta que se llegue a un punto de ruptuira para ejecutar el siguiente comando. Podemos a�adir retardos artificiales con comandos help repetidos, pero no hay garant�a de que el thread se suspenda cuando necesitamos que lo haga.

.�Crear un Di�logo de Sesi�n

Podemos usar una caracter�stica poco conocida de jdb para obtener un registro de nuestra sesi�n de depuraci�n. La salida es similar a la que ver�amos si ejecut�ramos jdb -dbgtrace.

Para permitir el diario jdb, creamos un fichero llamado .agentLog en el directorio donde est�mos ejecutando jdb o java -debug. En el fichero .agentLog, ponemos el nombre del fichero en el que se escriba la informaci�n de la sesi�n en la primera l�nea.Por ejemplo, un fichero .agentLog podr�a tener estos contenidos:

jdblog

Cuando luego ejecutamos jdb o java -debug, veremos que la informaci�n de sesi�n jdb se muestra de esta forma. Podemos usar esta informaci�n para recuperar los puntos de ruptura y los comandos introducidos por si necesitamos reproducir esta sesi�n de depuraci�n.

---- debug agent message log ----
[debug agent: adding Debugger agent to 
system thread list]
[debug agent: adding Breakpoint handler 
to system thread list]
[debug agent: adding Step handler to 
system thread list]
[debug agent: adding Finalizer to 
system thread list]
[debug agent: adding Reference Handler to 
system thread list]
[debug agent: adding Signal dispatcher to 
system thread list]
[debug agent: Awaiting new step request]
[debug agent: cmd socket: 
Socket[addr=localhost/127.0.0.1,
port=38986,localport=3 8985]]
[debug agent: connection accepted]
[debug agent: dumpClasses()]
[debug agent: no such class: HelloWorldApp.main]
[debug agent: Adding breakpoint bkpt:main(0)]
[debug agent: no last suspended to resume]
[debug agent: Getting threads for HelloWorldApp.main]

.�Depurar Servlets

Podemos depurar servlets con los mismos comandos jdb usados para depurar un applet o una aplicaci�n. JSDK "Java Servlet Development Kit" proporciona una programa llamado servletrunner que nos permite ejecutar un servlet sin un navegador web. En la mayor�a de los sistemas, este programa simplemente ejecuta el comando java sun.servlet.http.HttpServer. Por lo tanto, podemos arrancar la sesi�n jdb con la clase HttpServer.

Un punto importante a recordar cuando depuramos servlets es que el servidor Web Java y servletrunner realizan la carga y descargas de servlets, pero no incluyen el directorio servlets en el CLASSPATH. Esto significa que los servlets se cargan usando un cargador de clases personalizado y no por el cargador de clases por defecto del sistema.

.�Ejecutar servletrunner en Modo Depuraci�n

En este ejemplo, se incluye el directorio de ejemplos servlets en el CLASSPATH. Configuramos el CLASSPATH en modo depuraci�n de esta forma:

Unix

$ export CLASSPATH=./lib/jsdk.jar:./examples:$CLASSPATH  

Windows

$ set CLASSPATH=lib\jsdk.jar;examples;%classpath%

Para arrancar el programa servletrunner, podemos ejecutar el script de arranque suministrado llamado servletrunner o simplemente suministramos las clases servletrunner como par�metros de jdb. Este ejemplo usa el par�metro servletrunner.

$ jdb sun.servlet.http.HttpServer
Initializing jdb...
0xee2fa2f8:class(sun.servlet.http.HttpServer)
> stop in SnoopServlet.doGet
Breakpoint set in SnoopServlet.doGet
> run
run sun.servlet.http.HttpServer
running ...
main[1] servletrunner starting with settings:
port = 8080
backlog = 50
max handlers = 100
timeout = 5000
servlet dir = ./examples
document dir = ./examples
servlet propfile = ./examples/servlet.properties

Para ejecutar SnoopServlet en modo depuraci�n, introducimos la siguiente URL donde yourmachine es la m�quina donde arrancamos el servletrunner y 8080 es el n�mero d puerto mostrado en las selecciones de salida.

http://yourmachine:8080/servlet/SnoopServlet

En este ejemplo jdb para en la primera l�nea del m�todo doGet del servlet. El navegador espera una respuesta de nuestro servlet hasta que se pase el timeout.

main[1] SnoopServlet: init

Breakpoint hit: SnoopServlet.doGet (SnoopServlet:45)
Thread-105[1] 

Podemos usar el comando list para saber d�nde se ha parado jdb en el fuente.

Thread-105[1] list
41        throws ServletException, IOException
42        {
43      PrintWriter     out;
44    
45 =>   res.setContentType("text/html");
46      out = res.getWriter ();
47    
48      out.println("<html>");
49      out.println("<head>
                     <title>Snoop Servlet
                     </title></head>");
Thread-105[1] 

El servlet puede continuar usando el comando cont.

Thread-105[1] cont

.�Ejecutar el Java Web Server en Modo Depuraci�n

La versi�n JSDK no contiena las clases disponibles en el Java Web Server y tambi�n tiene su propia configuraci�n servlet especial. Si no podemos ejecutar nuestro servlet desde servletrunner, otra opci�n puede ser ejecutar el servidor web Java en modo depuraci�n.

Para hacer esto a�adimos la bandera -debug como el primer par�metro despu�s del programa java. Por ejemplo en el script bin/js cambiamos la l�nea Java para que se parezca a esto. En versiones anteriores de la plataforma java 2, tambi�n tendremos que cambiar el puntero del programa a la variable $JAVA a java_g en vez de a java.

Antes:

exec $JAVA $THREADS $JITCOMPILER $COMPILER $MS $MX \

Depu�s:

exec $JAVA -debug $THREADS $JITCOMPILER 
                                  $COMPILER $MS $MX \

Aqu� est� como conectar remotamente con el Java Web Server. La password de agente es generada sobre la slaida estandard desde el Java Web Server pero puede ser redirigida a un fichero en cualquier lugar. Podemos encontrar d�nde chequeando los scripts de arranque del Java Web Server.

jdb -host localhost -password <the agent password>

Los servlets se cargan por un cargador de clases separado si est�n contenidos en el directorio servlets, que no est� en el CLASSPATH usado cuando se arranc� el Java Web server. Desafortunadamente, cuando depuramos en modo remoto con jdb, no podemos controlar el cargador de clases personalizado y solicitarle que cargue el servlet, por eso tenemos que incluir el directorio servlets en el CLASSPATH para depurar o cargar el servlet requiri�ndolo a trav�s de un navegador y luego situando un punto de ruptura una vez que el servlet est� ejecutando.

En este siguiente ejemplo, se incluye el jdc.WebServer.PasswordServlet en el CLASSPATH cuando se arranca el Java Web server. El ejemplo selecciona un punto de ruptura para parar el m�todo service de este servlet, que es el m�todo de proceso principal.

La salida est�ndard del Java Web Server standard produce este mensaje, que nos permite seguir con la sesi�n remota de jdb:

Agent password=3yg23k

$ jdb -host localhost -password 3yg23k
Initializing jdb...
> stop in jdc.WebServer.PasswordServlet:service
Breakpoint set in jdc.WebServer.PasswordServlet.service
> stop
Current breakpoints set:
        jdc.WebServer.PasswordServlet:111

El segundo stop lista los puntos de ruptura actuales en esta sesi�n y muestra el n�mero de l�nea donde se encuentan. Ahora podemos llamar al servlet a trav�s de nuestra p�gina HTML. En este ejemplo, el servlet est� ejecutando una operaci�n POST:

<FORM METHOD="post" action="/servlet/PasswordServlet">
<INPUT TYPE=TEXT SIZE=15 Name="user" Value="">
<INPUT TYPE=SUBMIT Name="Submit" Value="Submit">
</FORM>

Obtenemos el control del thread del Java Web Server cuando se alcanza el punto de ruptura, y podemos continuar depurando usando las mismas t�cnicas que se usar�n en la secci�n Depuraci�n Remota.

Breakpoint hit: jdc.WebServer.PasswordServlet.service 
(PasswordServlet:111) webpageservice Handler[1] where
[1] jdc.WebServer.PasswordServlet.service 
                               (PasswordServlet:111)
[2] javax.servlet.http.HttpServlet.service 
                               (HttpServlet:588)
[3] com.sun.server.ServletState.callService 
                               (ServletState:204)
[4] com.sun.server.ServletManager.callServletService 
                               (ServletManager:940)
[5] com.sun.server.http.InvokerServlet.service 
                               (InvokerServlet:101)

Un problema com�n cuando se usan el Java WebServer y otros entornos de servlets es que se lanzan excepiones pero son capturadas y manejadas desde fuera del �mbito del servlet. El comando catch nos permite atrapar todas estas excepciones.

webpageservice Handler[1] catch java.io.IOException
webpageservice Handler[1] 
Exception: java.io.FileNotFoundException        
 at com.sun.server.http.FileServlet.sendResponse(
                                FileServlet.java:153)
 at com.sun.server.http.FileServlet.service(
                                FileServlet.java:114)
 at com.sun.server.webserver.FileServlet.service(
                                FileServlet.java:202)
 at javax.servlet.http.HttpServlet.service(
                                HttpServlet.java:588)
 at com.sun.server.ServletManager.callServletService(
			        ServletManager.java:936)
 at com.sun.server.webserver.HttpServiceHandler
           .handleRequest(HttpServiceHandler.java:416)
 at com.sun.server.webserver.HttpServiceHandler
           .handleRequest(HttpServiceHandler.java:246)
 at com.sun.server.HandlerThread.run(
                                HandlerThread.java:154)

Este sencillo ejemplo fue generado cuando los ficheros no se encontraban pero esta t�cnica puede usarse para problemas con datos posteados. Recordamos usar cont para permitir que el servidor web contin�e. Para limpiar est� trampa usamos el comando ignore.

webpageservice Handler[1] ignore java.io.IOException
webpageservice Handler[1] catch
webpageservice Handler[1] 

.�Depurar Eventos AWT

Antes del nuevo mecanismo de eventos del SWT presentado en el JDK 1.1 los eventos eran recibidos por un componente como un TextField, y propagado hacia arriba a sus componentes padre. Esto significa que podr�a simplemente a�adir alg�n c�digo de diagn�stico a los m�todo handleEvent o action del componente para monitorizar los eventos que le han llegado.

Con la presentaci�n del JDK 1.1 y el nuevo sistema de la cola de eventos, los eventos son enviados a una cola de eventos en lugar de al propio componente. Los eventos son despachados desde la cola de Eventos del Sistema a los oyentes de eventos que se han registrado para ser notificados cuando se despache un evento para ese objeto.

.�Usar AWTEventListener

Podemos usar un AWTEventListener para monitorizar los eventos AWT desde la cola de eventos del sistema. Este oyente toma una m�scada de evento construida desde una operaci�n OR de los AWTEvent que queremos monitorizar. Para obtener una simple lista de los eventos AWTEvent, usamos el comando javap -public java.awt.AWTEvent. Este ejemplo sigue la pista a los eventos de foco y del rat�n.

Nota: No se debe utilizar AWTEventListener en un producto para la venta, ya que degrada el rendimiento del sistema.

//EventTest.java
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class EventTest extends JFrame {

  public EventTest() {
    JButton jb1=new JButton("hello");
    getContentPane().add(jb1);

    //AWTEventListener
    getToolkit().addAWTEventListener(
      new AWTEventListener() {
        public void eventDispatched(AWTEvent e) {
          System.out.println(e+"\n");
        }
      }, AWTEvent.MOUSE_EVENT_MASK |
           AWTEvent.FOCUS_EVENT_MASK
       );
  }

  public static void main (String args[]) {

    EventTest et=new EventTest();
    et.setSize(300,300);
    et.pack();
    et.show();
  }
}

.�Analizar la Pila

Los desarrolladores siempre han considerado un misterio el seguimiento de pila. Hay muy poca o ninguna documentaci�n disponible, y cuando obtenenos una, o necesitamos generar una, el tiempo lo prohibe. La siguiente secci�n descubre los secretos de la depuraci�n con seguimiento de la pila, y al final, podremos considerar el seguimiento de pila como una herramienta �til para analizar otros programas -- no s�lo los que no funcionan!

�Qu� es un seguimiento de pila producido por la plataforma ? Es una imagen amigable para el usuario de los threads y monitores en la m�quina virtual Java. Dependiendo de lo compleja que sea nuestra aplicaci�n o applet, un seguimiento de pila puede tener un rango desde las cincuenta l�neas hasta los cientos de l�neas de diagn�stico.

Sin importar el tama�o del seguimiento de pila, hay unas pocas cosas importantes que nos pueden ayudar a diagnosticar la mayor�a de los problemas de software sin importar si somos expertos o nuevos en la plataforma Java.

Hay tres formas populares para generar un seguimiento de pila: enviar una se�al a la M�quina Virtual Java (JVM); la m�quina virtual java genera un seguimiento de pila por nosotros; o usar herramientas de depuraci�n o llamadas al API.

.�Enviar una Se�al a la JVM

En plataformas UNIX podemos enviar una se�al al programa con el comando kill. Est� es la se�al de salida, que es manejada por la m�quina virtual Java.

Sistemas Unix:

Por ejemplo, en la plataforma Solaris, podemos usar el comando kill -QUIT process_id, donde process_id es el n�mero de proceso de nuestro programa.

De forma alternativa podemos introducir la secuencia clave <ctrl>\ en la ventana donde se arranc� el programa.

El env�o de esta se�al instruye a un manejador de se�al de la JVM a que imprima recursivamente toda la informaci�n de los threads y monitores que hay dentro de la JVM.

Windows 95/NT:

Para generar un seguimiento de pila en plataformas Windows 95 o Windows NT, introducimos esta secuencia <ctrl><break> en la ventana donde se est� ejecutando el programa.

.�La JVM genera un Seguimiento de Pila

Si la JVM experiment� un error intermo como una violaci�n de segmento o una fallo de p�gina ilegal, llama a su propio manejador de se�ales para imprimir informaci�n sobre los threads y monitores.

.�Usar Herramientas de Depuraci�n o Llamadas al API

Podemos generar un seguimiento parcial de la pila, (que en este caso es s�lo informaci�n de los threads) usando el m�todo Thread.dumpStack, o el m�todo printStackTrace de la clase Throwable.

Tambi�n podemos obtener informaci�n similar introduciendo el comando where dentro del depurador Java.

Si tenemos �xito al generar un seguimiento de pila, podremos ver algo similar a esto seguimiento de pila.

strings core | grep JAVA_HOME

En la versiones Java 2, los threads que llaman a m�todos que resultan en una llamada a c�digo nativo son indicados en el seguimiento de pila.

.��Qu� Versi�n Genera el Seguimiento de Pila?

En la versi�n Java 2 el seguimiento de pila contiene la versi�n del JVM, la misma informaci� que veri�mos usando el par�metro-version.

Sin embargo si no hay string de versi�n, podemos obtener una idea sobre de qu� versi�n proviene este seguimiento de pila. Obviamente, si nosotros mismos hemos generado este seguimiento de pila no debe ser un problema, pero podr�amos estar viendo un seguimiento de pila posteado en un grupo de noticias o en un art�culo por e-mail.

Primero identificaremos donde est� la secci�n "Registered Monitor Dump" en el seguimiento de pila:

  • Si vemos un utf8 hash table lock en el "Registered Monitor Dump", esto es un seguimiento de pila de la plataforma Java 2. La versi�n final de la plataforma Java 2 tambi�n contiene un string de versi�n, por eso si no hay string de versi�n podr�a tratarse de una versi�n Beta de Java 2.
  • Si vemos un JNI pinning lock y no vemos utf8 hash lock, esto es una versi�n JDK 1.1+.

Si no aparece ninguna de las cosas anteriores en el "Registered Monitor Dump", probablemente ser� una versi�n JDK 1.0.2.

.��Qu� Plataforma Genera el Seguimiento de Pila?

Tambi�n podemos saber si el seguimiento de pila viene de una m�quina Windows 95, una NT, o UNIX buscando los threads que est� esperadno. En una m�quina Unix los threads que est�n esperando se llaman expl�citamente. En una m�quina Windows 95, o NT s�lo se muestra un contador de los threads que est�n esperando:

  • Windows 95/NT: Finalize me queue lock: <unowned> Writer: 1
  • UNIX: Finalize me queue lock: <unowned>

    waiting to be notified "Finalizer Thread"

.��Qu� Paquete Thread fue Utilizado?

Las JVMs de Windows 95 y Windows NT son por defecto threadas nativos del JVM. En UNIX las JVMs son por defectos, threads verdes de la JVM, usan una pseudo-implementaci�n thread. Para hacer que la JVM use threads nativos necesitamos suministrar el par�metro -native, por ejemplo, java -native MyClass.

Verificando la existencia de un Alarm monitor en la salida del seguimiento de pila podemos identificar que este seguimiento de pila viene de un thread verde la JVM.

.��Qu� son los Estados de Threads?

Veremos muchos threads diferentes en muy diferentes estados en una imagen del seguimiento de pila de JVM. Esta tabla descfribe varias claves y sus significados.

Clave Significado
R Thread runnable o ejecut�ndose
S Thread suspendido
CW Thread esperando en un condici�n variable
MW Thread esperando un bloqueo de monitor
MS Thread suspendido esperando un bloqueo de monitor

Normalmente, s�lo los threadas en estados R, S, CW o MW deber�an aparecer en el seguimiento de pila.

Los monitores se usan para controlar el acceso a c�digo que s�lo deber�a ser ejecutado por un s�lo thread a la vez. Monitores se cubren en m�s detalles en la siguiente secci�n. Los otros dos estados de threads comunes que podr�amos ver son R, threads ejecutables y CW, threads en una condici�n de estado de espera. Los threadas ejecutables son por definici�n threads que podr�an ser ejecutados o estar ejecut�ndose en ese momento. En una m�quina multi-procesador ejecut�ndo un sistema operativo realmente multi-procesador es posible que todos los threads ejecutables se est�n ejecutando en el mismo momento. Sin embargo es m�s probable que otros threads ejecutables est�n esperando un programador de threads para tener su turno de ejecuci�n.

Podr�amos pensar en los threads en una condici�n de estado de espera como esperando a que ocurra un evento. Frecuentemente un thread aparecer� en el estado CW si est� en un Thread.sleep o en una espera sincronizada. En nuestro anterior seguimiento de pila el m�todo main estaba esperando a que un thread se completara y se notificara su finalizaci�n. En el seguimiento de pila esto aparecer� como:

"main" (TID:0xebc981e0, sys_thread_t:0x26bb0, 
				state:CW) prio=5
 at java.lang.Object.wait(Native Method)
 at java.lang.Object.wait(Object.java:424)
 at HangingProgram.main(HangingProgram.java:33)

El c�digo que cre� este seguimiento de fila es este:

  synchronized(t1) {
    try {
      t1.wait();    //line 33
    }catch (InterruptedException e){}
  }

En la versi�n Java 2 las operaciones de monitores, incluyendo nuestra espera aqu�, son manejadas por la m�quina virtual Java a trav�s de una llamada JNI a sysMonitor. La condici�n de espera de un thread se mantiene en una cola de espera de monitor especial del objeto que est� esperando. Esto explica porqu� aunque seamos los �nicos esperando por un objeto el c�digo todav�a necesita estar sincronizado con el objeto como si estuviera utilizano de hecho el monitor de ese objeto.

.�Examinar Monitores

Esto nos trae la otra parte del seguimiento de pila: el volcado de monitores. Si consideramos que la secci�n de threads de un seguimiento de pila identifica la parte multi-thread de nuestra aplicaci�n, entonces la secci�n de monitores representa las partes de nuestra aplicaci�n que usan un s�lo thread.

Podr�a ser sencillo imaginar un monitor como un lavadero de coches. En muchos lavaderos de coches, s�lo se puede lavar un coche a la vez. En nuestro c�digo Java s�lo un thread a la vez puede tener el bloqueo sobre una pieza sincronizada de c�digo. Todos los dem�s threads esperan en la cola para entrar al c�digo sincronizado como lo hacen los coches para entrar en el lavadero de coches.

Se puede pensar en un monitor como un bloqueo para un objeto, y cada objeto tiene un monitor. Cuando generamos un seguimiento de pila, los monitores se listan como registrados o no registrados. En la mayor�a de los casos estos monitores registrados, o monitores del sistema, no deber�an ser la causa de nuestro problema de software, pero nos ayudar�n a entenderlos y reconocerlos. La siguiente tabla describe los monitores registrados mas comunes:

MonitorDescripci�n
utf8 hash table Bloquea el hashtable de Strings i18N definidos que fueron cargados desde la clase constant pool.
JNI pinning lock Protege las copias de bloques de array a c�digo de m�todos nativos.
JNI global reference lock �Bloquea la tabla de referencias globales que contiene los valores que necesitan ser liberado expl�citamete, y sobrevivir� al tiempo de vida de la llamada del m�todo nativo.
BinClass lock Bloquea el acceso a la lista de clases cargadas y resueltas. La tabla global de lista de clases.
Class linking lock Protege datos de clases cuando se cargan librer�as nativas para resolver referencias simb�licas
System class loader lock Asegura que s�lo un thread se carga en una clase del sistema a la vez.
Code rewrite lock Protege el c�digo cuando se intenta una optimizaci�n.
Heap lock Protege la pila Java durante el manejo de memoria de la pila.
Monitor cache lock S�lo un thread puede tener acceso al monitor cache a la vez este bloqueo asegura la integridad del monitor cache.
Dynamic loading lock Protege los threads verdes de la JVM Unix de la carga de librer�a compartida stub libdl.so m�s de uno a la vez.
Monitor IO lock Protege I/O f�sica por ejemplo, abrir y leer.
User signal monitor Controla el acceso al controlador de se�al si hay una se�al de usuario en un thread verde de la JVM.
Child death monitor Controla accesos al proceso de informaci�n de espera cuando usamos llamadas al sistema de ejecuci�n para ejecutar comandos locales en un thread verde de la JVM.
I/O Monitor Controla accesos al fichero descriptor de threadas para eventos poll/select.
Alarm Monitor Controla accesos a un controlador de reloj usado en threads verdes de la JVM para manejar timeouts
Thread queue lock Protege la cola de threads activos.
Monitor registry S�lo un thread puede tener acceso al registro de monitores al mismo tiempo que este bloqueo asegura la integridad de este registro.
Has finalization queue lock * Protege la lista de objetos bloqueados que han sido recolectadas para la basura, y considera la finalizaci�n necesaria. Son copiados a la cola Finalize.
Finalize me queue lock * Protege una lista de objetos que pueden ser finalizados por desocupados.
Name and type hash table lock * Protege las tablas de constantes de las JVMs y sus tipos.
String intern lock * Bloquea la hashtable de Strings definidos que fueron cargadas desde la clase constant pool
Class loading lock * Asegura que s�lo un thread carga una clase a la vez.
Java stack lock * Protege la lista de segmentos libres de la pila

Nota: * bloqueo aparecidos s�lo en los seguimientos de pre-Java 2.

El propio registro de monitores est� protegido por un monitor. Esto significa que el thread al que pertenece un bloqueo es el �ltimo thread en usar un monitor. Es como decir que este thread es el thread actual. Como s�lo un thread pueden entrar en un bloque sincronizado a la vez, otros threads se ponen a la cola para entrar en el c�digo sincronizado y aparecen con el estado MW. En el volcado del cach� de monitores, se denotan como threads "esperando para entrar". En el c�digo de usuario un monitor es llamado a acci�n siempre que se usa un bloque o m�todo sincronizado.

Cualquier c�digo que espere un objeto o un evento (m�todo que espera) tambi�n tiene que estar dentro de un bloque sincronizado. Sin emabrgo, una vez que se llama a este m�todo, se entrega el bloqueo sobre el objeto sincronizado.

Cuando el thread en estado de espera es notificado de un evento hacia el objeto, teine la competencia del acceso exclusivo a ese objeto, y tiene que obtener el monitor. Incluso cuando un thread a enviado un "notify event" a los threads que est�n esperando, ninguno de estos threads puede obtener realmente le control del monitor bloqueado hasta que el thread notificado haya abandonado el bloque de c�digo sincronizado.

.�Poner los Pasos en Pr�ctica

Ejemplo 1

Consideremos un problema de la vida real como por ejemplo el Bug ID 4098756. Podemos encontrar m�s detalles sobre este bus en el JDC Bug Parade. Este bug documenta un problema que ocurre cuando usamos un componente Choice sobre Windows 95.

Cuando el usuario selecciona una de las opciones desde el componente Choice usando el rat�n, todo va bien. Sin embargo, cuando el usuario intenta usar una tecla de fleca paramover la lista de opciones, la aplicaci�n Java se congela.

Afortunadamente, este problema es reproducible y hab�a un seguimiento de pila Java para ayudar a corregir el problem. El seguimiento de pila completo est� en la p�gina del bug, pero s�lo necesitamos enfocarnos en estos dos threads claves:

"AWT-Windows" (TID:0xf54b70, 
sys_thread_t:0x875a80,Win32ID:0x67, 
state:MW) prio=5
java.awt.Choice.select(Choice.java:293)
sun.awt.windows.WChoicePeer.handleAction(
                              WChoicePeer.java:86)

"AWT-EventQueue-0" (TID:0xf54a98,sys_thread_t:0x875c20,
Win32ID:0x8f, state:R) prio=5
java.awt.Choice.remove(Choice.java:228)
java.awt.Choice.removeAll(Choice.java:246)

El thread AWT-EventQueue-0 est� en estado ejecutable dentro del m�todo remove. Remove est� sincronizado, lo que expl�ca por qu� el thread AWT-Windows no puede entrar al m�todo select. El thread AWT-Windows est� en estado MW (monitor wait); sin embargo, sin embargo si seguimos el seguimiento de pila, esta situaci�n no cambia aunque el interface gr�fico de usuario (GUI) parezca est�r congelado.

Esto indica que la llamada a remove nunca retorn�. Siguiendo el camino del c�digo hacia la clase ChoicePeer, podemos ver que se est� haciendo a un llamada al MFC nativo que no retorna, Es aqu� donde est� el problema real y es un bug de las clases coraz�n Java. El c�digo del usuario esta bien.

Ejemplo 2

En este segundo ejemplo investigaremos un bug que al principio parece ser un fallo de Swing pero descubriremos que es debido al hecho que Swing no es seguro ante los threads.

El informa de bug tambi�n est� disponible en la site JDCm el n�mero del bug es 4098525.

Aqu� tenemos un ejemplo del c�digo usado para reproducir este problem. El d�alogo modal se crea desde dentro del m�todo JPanel paint.

import java.awt.event.*;
import java.awt.*;
import java.util.*;
import javax.swing.*;

class MyDialog extends Dialog 
                         implements ActionListener {

    MyDialog(Frame parent) {
        super(parent, "My Dialog", true); 
        Button okButton = new Button("OK");
        okButton.addActionListener(this);
        add(okButton);
        pack();
    }

    public void actionPerformed(ActionEvent event) {
         dispose();
    }
}

public class Tester extends JPanel {

    MyDialog myDialog;
    boolean firstTime = true;

    public Tester (JFrame frame) throws Exception {
        super();
	myDialog = new MyDialog(frame);
    }

    void showDialogs() {
        myDialog.show();
    }

    public void paint(Graphics g) {
        super.paint(g);
        if (firstTime) {
           firstTime = false;
           showDialogs();
        }
    }

    public static void main(String args[]) 
                              throws Exception {

       JFrame frame = new JFrame ("Test");
       Tester gui = new Tester(frame);
       frame.getContentPane().add(gui);
       frame.setSize(800, 600);
       frame.pack();
       frame.setVisible(true);
    }
}

Cuando ejecutamos este programa encontramos que se bloquea al principio. Haciendo un seguimiento de pila podremos ver estos threads claves.

El seguimiento de pista que tenemos aqu� es ligeramente diferente al que aparece en el informe del bug, pero tienen el mismo efecto. Tambi�n usamos la versi�n Java 2 para generar el seguimiento y suministrar la opci�n -Djava.compiler=NONE cuando ejecutamos el programa para que podams ver los n�meros de l�nea del fuent. El thread a buscar es el que tiene el estado MW, monitor de espaera que en este caso es el threadAWT-EventQueue-1

"AWT-EventQueue-1" (
       TID:0xebca8c20, sys_thread_t:0x376660, 
				state:MW) prio=6
 at java.awt.Component.invalidate(Component.java:1664)
 at java.awt.Container.invalidate(Container.java:507)
 t java.awt.Window.dispatchEventImpl(Window.java:696)
 at java.awt.Component.dispatchEvent(
                          Component.java:2289)
 at java.awt.EventQueue.dispatchEvent(
                          EventQueue.java:258)
 at java.awt.EventDispatchThread.run(
                          EventDispatchThread.java:68)

Si buscamos est� l�nea en el fichero java/awt/Component.java que est� contenido en el archivo src.jar, veremos esto:

    public void invalidate() {
        synchronized (getTreeLock()) { //line 1664

Es aqu� donde nuestra aplicaci�nse bloquea, est� esperando a que el monitor getTreeLock se libere. La siguiente tarea es encontrar el thread que tiene bloqueado este monitor.

Para ver qui�n est� bloqueando este monitor buscamos en el volcado del cache de Monitores y en este ejemplo podemos ver lo siguiente:

Monitor Cache Dump:
  java.awt.Component$AWTTreeLock@EBC9C228/EBCF2408: 
	owner "AWT-EventQueue-0" ( 0x263850) 3 entries
  Waiting to enter:
    "AWT-EventQueue-1" (0x376660)

El monitor getTreeLock est� actualmente bloqueado en un objeto de una clase interna creada especialmente AWTTreeLock. Este es el c�digo para crear ese bloqueo en el fichero Component.java.

    static final Object LOCK = new AWTTreeLock();
    static class AWTTreeLock {}

El propietario actual es AWT-EventQueue-0. El thread llam� a nuestro m�todo paint para crear nuesto Dialog modal mediante una llamada a paintComponent. El propio paintComponent fue llamado desde una llamada a update del JFrame.

�Pero d�nde se origin� el bloqueo? Bien, ni hay una forma sencilla de encontrar qu� parte del marco tiene el bloqueo pero una simple b�squeda de javax.swing.JComponent podremos ver que getTreeLock es llamado dentro del m�todo paintChildren que dejamos en la l�nea 388.

at Tester.paint(Tester.java:39)
at javax.swing.JComponent.paintChildren(
                            JComponent.java:388)

El resto del puzzle se coloca junto analizando el m�todo MDialogPeer show. El c�digo del di�logo crea un nuevo ModalThread que es por lo que hemos visto un thread AWT-Modal en la salida del seguimiento de pila, este thread es usado para postear el di�logo. Es cuando el evento de despacha usando AWT-EventQueue-1 que es usado para ser el proxy de despacho de eventos de AWT y es necesario un acceso al monitor getTreeLock y es aqu� donde tenemos el bloqueo.

Desafortunadamente el c�digo Swing no est� dise�ado para ser seguro con los threads por eso la soluci�n en este ejemplo es no crear di�logos modales desde dentro de m�todo paint de Swing. Ya que Swing tiene que hacer cantidad de bloqueos y c�lculos; que las partes de un componente ligero que necesitan ser dibujadas deben estar fuertemente advertidas de que no incluyan c�digo sincronizado o c�digo que puede resultar en una llamada sincronizadac como en un di�logo modal, dentro del m�todo paint.

Esto completa la teoria del seguimiento de pila Java, y ahora deber�amos saber qu� busar la siguiente vez que veamos un seguimiento de pila. Para ahorrar tiempo, deber�amos hacer uso de la b�squeda de Bugs del JDC para ver si nuestro problema ha sido reportado por alguien m�s.

.�Lista de chequeo del Experto

Para sumarizar, estos son los pasos a tomar la proxima vez que nos crucemos con un problema en un programa Java:

  • Programas Colgados, bloqueados o congelados: Si pensamos que nuestro programa est� colgado, generamos un seguimiento de pila. Examinamos los threads en estados MW o CW. Si el programa est� bloqueado, algunos threads del sistema se nos mostrar�n como el thread actual porque la JVM no tendr� nada m�s que hacer.
  • Programas Cascados o Abortados: Sobre Unix buscaremos por un fichero coraz�n. Podemos analizar este fichero en una herramienta de depuraci�n nativa como gdb o dbx. Buscamos los threads que hayan sido llamados por m�todo nativos. Como la tecnolog�a Java usa un modelo de memoria seguro, cualquier posible corrupci�n habr� ocurrido en el m�todo nativo. Recordamos que la JVM tambi�n usa c�digo nativo por lo que bien podr�a no ser un bug de nuestra aplicaci�n.
  • Programas ocupados: El mejor curso de acci�n que podemos tomar para los programas ocupados es generar frecuentes seguimientos de pila. Esto nos apuntar� hacia el c�digo que est� causando los errores, y podrmos empezar nuestra investigaci�n desde aqu�.

.�Problemas de Versiones

Esta secci�n proporciona una tabla que sumariza los problemas y soluciones relacionados con la tenencia de distintas versiones de la plataforma Java instalados en nuesto sistema.

Producto Desarrollo
JDK 1.0.2 Utiliza CLASSPATH para encontrar y cargar las clases coraz�n del sistema.

En Windows 95:

CLASSPATH=/usr/java/lib/classes.zip:.

En Unix:

CLASSPATH=c:\java\lib\classes.zip

Las librer�as din�micas Unix, los ficheros .dll, los objetos compartidos y fichero .so est�n localizados en la variable PATH.

Efectos laterales:

El fichero Autoexec.bat de Win95 contiene una variable CLASSPATH caducada seleccionad por el usuario o la instalaci�n de otras aplicaciones.

El Entorno de usuario de WinNt contiene un vieja variable CLASSPATH.

Los scripts Unix .cshrc, .profile, o .login contiene un CLASSPATH err�neo.

La vari�ble de entorno JAVA_HOME tambi�n es usada por programas para comprobar si no est� seleccionada. Podemos borrar este campo en el shel Bourne (sh) de esta forma: unset JAVA_HOME

Diagn�sticos:

Usamos la opci�n -classpath para forzar a la m�quina virtual Java a que use s�lo la l�nea de comandos. S�lo CLASSPATH: java -classpath c:\java\lib\classes.zip;. myapp

Producto Desarrollo
JDK 1.1 Usa paths relativos para encontrar el fichero classes.zip desde la instalaci�n de la plataforma Java. La variable de entorno CLASSPATH se usa para cargar las clases de la aplicaci�n.

Efectos laterales:

Otras versiones Java encontradad en el path de la aplicaci�n podr�an ser cargadas si el directorio bin del JDK no se selecciona expl�tamente delante de la variable de entorno PATH.

Diagn�sticos:

Usamos la opci�n -classpath para forzar a la m�quina virtual Java a que use s�lo la l�nea de comandos. S�lo CLASSPATH: java -classpath c:\java\lib\classes.zip;. myapp

Producto Desarrollo
Plataforma

Java 2

La plataforma est� dividida en un Entorno de Ejecuci�n Java (JRE) y un compilador Java. El JRE est� incluido como un subdirectorio de la versi�n, y los tradiciones programas java y javac del directorio bin llaman directamente el programa real en el directorio jre/bin.

Los archivos JAR que contienen las clases del sistema de la plataforma Java, rt.jar y i18.jar, est�n localizados en el directorio jre/lib con un path de b�squeda relativo.

Efectos Laterales:

Si las aplicaciones anteriores usaban el fichero classes.zip para cargar las clases del sistema de la plataforma Java, podr�a intentar cargar err�neamente un conjunto de clases adicionales.

Diagn�sticos:

Usamos la opci�n -Xbootclasspath para forzar al m�quina virtual Java a usar el CLASSPATH sumnistrado en la l�nea de comandos:java -Xbootclasspath:c:\java\jre\lib\rt.jar;

c:\java\jre\lib\i18n.jar;. myapp

Podr�amos necesitar suministrar esto como una opci�n de la l�nea de comandos de esta forma:

javac -J-Xbootclasspath:c\java\lib\tools.jar;c:

\java\jre\lib\rt.jar;c:\java\jre\lib\i18n.jar;. myapp.java

Producto Desarrollo
Java Plug-In Sobre Windows 95 y Windows NT usamos el registro para encontrar plug-in de la plataforma Java instalados.

Efectos Laterales:

El registro podr�a estar corrompido, o el plug-in eliminado f�sicamente pero no del registro.

Diagn�sticos:

Mostrar las propiedades java.version y java.class.path en nuesto c�digo y verlo en la Consola del Java Plug-in Console

System.out.println("version="+System.getProperty(
  "java.version"
  ));
System.out.println("class path="+System.getProperty(
  "java.class.path"
  ));

Si hay un conflicto, chequeamos el registro con el comando regedit, buscamos la palabra VM, y si existe la borramos y reinstalamos el plug-in.

Producto Desarrollo
Netscape usa ficheros .jar como java40.jar del directorio netscape.

Efectos Laterales:

No todas las versiones de Netscape son totalmente compatibles con JDK 1.1. Podemos obtener actualizaciones en http://www.netscape.com.

Diagn�sticos:

Arrancamos el navegador desde la l�nea de comandos con la opci�n -classes.

Producto Desarrollo
Internet

Explorer

Usa ficheros .cab para contener las clases del sistema. Tambi�n usa el registro del sistema sobre Windows 95/NT.

Efectos Laterales:

Usamos el comando regedit para buscar la palabra VM. Esa es la entrada CLASSPATH donde podemos a�adir nuestras propias clases.

Diagn�sticos:

El registro puede corromperse. Buscamos CLASSPATH usando el programa regedit y editamos el valor al que apunta CLASSPATH.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO