Programación en castellano
Inicio > Tutoriales > J2EE > Escribir Aplicaciones Avanzadas para la Plataforma Java 2
-Tutoriales

Escribir Aplicaciones Avanzadas para la Plataforma Java 2


Depuración de Applets, Aplicaciones y Servlets

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.

 
Patrocinados
 

Copyright © 1999-2007 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad

Hospedaje web y servidores dedicados linux por Ferca Network

red internet: musica mp3 | logos y melodias | hospedaje web linux | registro de dominios | servidores dedicados
más internet: comprar | recursos gratis | posicionamiento en buscadores | tienda virtual | gifs animados