Copyright ©1996, Que Corporation. All rights reserved. No part of this book may be used or reproduced in any form or by any means, or stored in a database or retrieval system without prior written permission of the publisher except in the case of brief quotations embodied in critical articles and reviews. Making copies of any part of this book for any purpose other than your own personal use is a violation of United States copyright laws. For information, address Que Corporation, 201 West 103rd Street, Indianapolis, IN 46290 or at support@mcp .com.

Notice: This material is excerpted from Special Edition Using Java, ISBN: 0-7897-0604-0. The electronic version of this material has not been through the final proof reading stage that the book goes through before being published in printed form. Some errors may exist here that are corrected before the book is published. This material is provided "as is" without any warranty of any kind.

Chapter 17 - Debugging Your Java Code

by Amber Benson

In this chapter, you learn

jdb is a command-line debugger for Java applications and applets. By using a debugger, you can avoid inserting print statements throughout your code, which you have to remove later, to learn the values of variables during execution. Instead you set breakpoints, jdb stops at the breakpoints, and you can print out variable values via commands to jdb. If you discover you have not pinpointed the location of a bug, you can then set new breakpoints, or move through the code at various granularities: you can continue to the next breakpoint, or execute the next line of code, or step into any function which may be called in the next line of code. You can always list the code surrounding the point at which the code is stopped. You can also examine the call stack at any time the code is stopped or suspended.

Using jdb you can move through the threads in your code with ease. You can focus jdb's attention on any given thread, and you can suspend and resume execution of any or all threads independently. This gives you a fine control over your program which may allow you to solve bugs which would otherwise be extremely difficult to find.

You can also instruct jdb to catch exceptions, whether they are caught in your program or not. Once caught, you can then examine variables and the stack, just as if you had set a breakpoint at that point in the code.

You can use jdb to debug either a stand-alone application or an applet, using the appletviewer in debug mode. You can use jdb to inspect and debug your programs running under either a local or a remote Java interpreter.

An example-StockTicker program

The example used in this chapter is a StockTicker program-a stand-alone application, not an applet. It continuously reads stock market price data and displays it in its own window. For brevity's sake we have removed any code that sized the window-it appears as merely a window frame and you must resize it with your mouse. At regular intervals, the StockTicker moves the display left one character, thus giving the appearance of a stock ticker. In a real live stock ticker application the data would be from an incoming data source, probably on the Net, but in this example it comes from static data and just simulates incoming data. We did this so you can run the example locally, without having a network connection or access to a stock quotation service. The StockTicker has three threads-the main thread, a StockReader, and a ShiftPainter.

import java.awt.*;
import java.net.URL;
public class TheStockTicker {
        public static void main( String[] argv ) {
               Frame          mainWin;       // Top level window
               StockTicker    ticker;        // Ticker object
               mainWin = new Frame( "Stock Ticker" );
               mainWin.setLayout( new BorderLayout() );
               ticker = new StockTicker();
               ticker.show();
               mainWin.add( "Center", ticker );
               mainWin.show();
               mainWin.layout();
               ticker.begin();
        }
}
 class StockTicker extends java.awt.Canvas {
        static        int     MAXLENGTH = 1000;      // max length ticker string
        static        int     HZMARGIN = 20;         // horizontal margin space
        static        int     VTMARGIN = 5;          // vertical margin space
        String                tickerStr = null;      // text left to print
        int                   paintX, paintY;        // x origin and Y origin (baseline) for painting
        ShiftPainter          shifter = null;        // thread controlling painting of ticker
        boolean               paused = false;        // true => ticker is not scrolling
        Thread                reader = null;         // thread controlling reading of stock data
        public void begin() {
                // Get the stock data feed going
                if (reader == null) {
                        reader = new StockReader( this );
                        reader.start();
                }
                // Get the shifter going
                if (shifter == null) {
                        shifter = new ShiftPainter( this );
                        shifter.start();
                }
                return;
        }
        public void stop() {
                shifter.stop();
                reader.stop();
                return;
        }
        public void paint(Graphics g) {
                if (tickerStr != null) {
                        if ((paintX == 0) && (paintY == 0)) {
                                paintY = g.getFontMetrics().getMaxAscent() + VTMARGIN;
                                paintX = HZMARGIN;
                        }
                        g.drawString( tickerStr, paintX, paintY );
                }
                return;
        }
        public synchronized boolean shiftData() {
                boolean         shifted;               // true => data was actually shifted
                shifted = false;
                                                       // Determine if we need to shift (text is longer than display area)
                if (tickerStr != null && getGraphics().getFontMetrics().stringWidth( tickerStr ) + 
                        2 * HZMARGIN > size().width) {
                        if ( tickerStr.length() >= 1) {        // Do the shift
                                tickerStr = tickerStr.substring( 1 );
                                shifted = true ;
                        }
                }
                return( shifted );
        }
        public synchronized void append( String data ) {
                if (tickerStr == null)                // Fill up the ticker string
                        tickerStr = data;
                else
                        tickerStr = tickerStr + data;
                if (tickerStr.length() > MAXLENGTH)   // Truncate if string growing too big
                        tickerStr = tickerStr.substring( tickerStr.length() - MAXLENGTH );
                repaint();
                if (shifter != null && shifter.isWaiting())
                        shifter.resume();
                return;
        }
}
class StockReader extends Thread {
        StockTicker           tickerData;              // object holding the ticker data
        static final String[] dummyData = { "PARQ: 8.5", "PARQ: 10.2", "PARQ: 7.3" };
        static int                i = 0;
        StockReader( StockTicker t)        { tickerData = t; }
        public void run() {String newData;             // new data read from stock info service
        URL stockURL;                                  // connection to stock data server
        Object dataPipe;                               // content from stock data server
        do {newData = readTicker();                    // Fill up the ticker string
        if (newData != null)tickerData.append( "" + newData );
        try {                                          // Wait for the next time to poll sleep(10000);
        }catch (InterruptedException e) {
        }} while (newData != null);
        }String readTicker() {StringpollData;
                                                       // data from poll of stock info
                pollData = dummyData[i];
                i = ++i % 3;
                return( pollData );
        }}class ShiftPainter extends Thread {
        StockTicker tickerWin; 
                                                       // Window to paint in boolean stalled; 
                                                       // true => we are stalled, waiting for more text
        ShiftPainter( StockTicker st ){ tickerWin = st; stalled = false; }
        public void run() {do {try {sleep(100);
                        }
                        catch (InterruptedException e) {
                        }
                        tickerWin.repaint();
                                                       // Shift string one position to the left
                        if (tickerWin.shiftData() == false) {        
                                stalled = true;
                                suspend();
                        }
                } while ( true ) ;
        }
        public boolean isWaiting() {
                return( stalled );
        }
}
(c)Preparing for Debugging

If you are going to look at local (stack) variables in your debug session, you must first compile your classes with the -g option:

javac -g <className>.java

The resulting compilation of your application is unoptimized byte code, which jdb needs to access the symbol tables for local variables.

Do not confuse javac_g with javac -g. javac_g is an unoptimized version of javac, and as such is suitable for use if you think you are having compiler problems or are doing a port, so that you might need to debug the compiler itself. javac_g does not produce unoptimized byte code of your application, which is what you need to debug your application.

Starting jdb

Since jdb is command-line oriented, you start jdb from the prompt in an xterm or an MS-DOS prompt window. Typically you start a jdb session in the directory which contains the classes you intend to debug, although that is not necessary (see the -classpath option, below). You can start a jdb debugging session in one of two ways. You can have jdb start the Java interpreter with the class to be debugged, or you can start jdb and ask it to attach to a running Java interpreter. jdb accepts the same options as the Java interpreter; it also has a few of its own, as shown in the following sections.

Starting jdb to Debug a Complete Application

To start jdb and the Java interpreter in one command (jdb starts the Java interpreter), use:

jdb <className>

Once you start jdb, jdb responds in the xterm or MS-DOS window from which you started jdb with three lines similar to the following:

Initializing jdb...
0x139f408:class(TheStockTicker)
>

Since none of your code has yet executed, you see nothing more than these three lines in the xterm. Used this way, jdb starts the Java interpreter with any specified command line arguments, loads the specified class, and stops before executing the class's first executable instruction.

If your classes are in a different directory or directories, start jdb with the command line argument classpath, specifying all the directories jdb should look in for classes:

jdb -classpath <classPath> <className>

You must specify the complete path to classes.zip in the classpath argument, in addition to the path to your classes. For example, on a Windows system:

jdb -classpath C:/mystuff/myclasses;C:/java/java1_0/lib/classes.zip MyApp

And on a UNIX system:

jdb -classpath /usr/mystuff/myclasses:/usr/java/java1_0/lib/classes.zip MyApp

You can also start the program with the Java interpreter, then start jdb as a separate process and attach it to the running program. To do this, you must start the execution of your program with the -debug option. Doing this will cause Java to print a password before beginning execution of the program. The password can then be used as a command line argument to jdb; jdb will attach to the process with that password. If you wish to run the Java program on one machine and jdb on a different one, use the
-host option when starting jdb:

java -debug <className>
jdb [-host <hostname>] -password <password>

If you have a program which takes input from the standard input, you must use this second method to debug it. Once jdb is started, any input typed is treated as input to jdb. Using the second method, you can have one input stream for your program, and one for jdb.(d)Starting jdb to Debug an Applet

To debug an applet using jdb, start the appletviewer with the
-debug command line argument. This starts jdb, which then starts the appletviewer but stops before any executable appletviewer line is executed. Once you type run, execution in appletviewer begins, and each applet in the HTML file is started in a separate window:

appletviewer -debug <htmlFileName>

Once you start the appletviewer in debug mode, jdb responds with three lines similar to the following:

Initializing jdb...
0x139f408:class(sun.applet.AppletViewer)
>

Since none of your code has yet executed, you see nothing except these three lines in the xterm.

The jdb Prompt

If you start jdb with no command line arguments, whether or not you have loaded a class, the jdb prompt is the following:

>

However, once you type run, the prompt is the name of the thread which has jdb's attention. By default, this is the main thread, and the jdb prompt is:

main[1]

If you point jdb at another thread using the thread command, the prompt changes to the that of the new thread. Using the StockTicker example, the command thread 5 causes the prompt to change to the following:

Screen Updater[1]

The number in brackets indicates the stack level context. If you give the up or down command, the number in brackets changes to show the stack level. Continuing with the StockTicker example, after the command thread 5, a command of up 2 causes the prompt to change to:

Screen Updater[3]

Each time you enter a command, jdb executes the command and then redisplays the prompt, regardless of the state of the program being debugged.

Each of the commands listed in the remainder of this chapter is a command to be typed at the jdb prompt.

Specifying the Source Path and Loading Classes

The command use with no argument causes jdb to list the path it is using to find the source code. use followed by a file path changes the source file path to the one specified. This is the path jdb follows to find the source code (*.java) when listing code in response to the list command:

use [source file path]

If you started jdb with no arguments, or if you wish to load a new class that that has not yet been loaded as a result of program execution, use load <className>:

load <className>

Listing Parts of the Program and Program Organization

You can use most of these commands regardless of the state of the program. They return correct responses whether the program is running, stopped at a breakpoint, or any or all threads are suspended. The only exception to this is the list command, which does not list source code unless the program is suspended or stopped at a breakpoint.

classes

The classes command causes jdb to list all the currently known classes. This includes all the Java classes, as well as the classes in your program.

methods

You must specify a class name with the methods command; jdb lists all the methods for the specified class:

methods <class id>

Using the StockTicker example, the command methods ShiftPainter causes jdb to list the following:

void <init>(StockTicker)
void run()
boolean isWaiting()

list [line number]

In order to list lines of source code, the program must be stopped at a breakpoint or you must have already specified a thread of interest and have suspended that thread. If you use the list command at any other time, jdb responds with the following:

No thread specified.
or
Current thread isn't suspended.

A command of list with no argument causes jdb to list the source code surrounding the breakpoint or the point at which the thread was suspended. list with a line number argument causes jdb to list the source code surrounding the specified line.

If the thread is suspended rather than stopped at a breakpoint, in order for the context to be correct so that the list command can find the correct code to list, you must specifically set the thread context via the thread command, and then you may need to move up the stack using the up command to set the stack context to your program's code.

If the source for the classes being debugged is not in the default classpath (or the classpath specified by the -classpath command line argument), jdb cannot list the source. See the use command above for specifying a file path to the source.

memory

The memory command causes jdb to report the memory usage of the program at that point in time. Both the free memory and the total memory is reported.

Threads

You can examine all the threadgroups and threads currently in existence in your program. You can also change jdb's focus from one thread to another, and suspend and resume execution of any or all threads. You may, for example, need to suspend one thread while you step through another to a certain point, then resume the first thread, examining variable values along the way.

threadgroups

The threadgoups command lists all the threadgroups in the program. They are listed prefaced by an incremental index number, followed by the class name (java.lang.ThreadGroup), followed by the threadgroup handle, followed by the threadgroup name. Only threadgroups of threads which have been started are shown.

Using the StockTicker example, the threadgroups command produces the following output:

1. (java.lang.ThreadGroup)0x13930b8 system
2. (java.lang.ThreadGroup)0x13939c0 main
3. (java.lang.ThreadGroup)0x13a0b50 TheStockTicker.main

threads [threadgroup]

Typing threads causes jdb to list all the threads in the default threadgroup, which is normally the first non-system group. threads <threadgroup> causes jdb to list all the threads in the specified threadgroup. Use the threadgroup name (the last element in a threadgroups command output line) to specify the threadgroup in the threads command. Threads are listed prefaced by an incremental index number. The last item on the thread line is the condition of the thread. Only the threads which have been started are shown.

Using the StockTicker example, the command threads TheStockTicker.main issued while the stock ticker program is running produces the following output:

Group TheStockTicker.main:
 1. (java.lang.Thread)0x13a09f8      AWT-Win32          running
 2. (java.lang.Thread)0x13a0a50      AWT-Callback-Win32 running
 3. (StockReader)0x13a0cb0           Thread-2           cond. waiting
 4. (ShiftPainter)0x13a0d10          Thread-3           running
 5. (sun.awt.ScreenUpdater)0x13a0da8 Screen Updater     cond. waiting 

suspend [thread id(s)]

The suspend command with no arguments suspends all non-system threads. suspend followed by one or more thread ids suspends those threads only. The thread id argument is the index number of the thread in the current threadgroup; the indices begin at 1 in each threadgroup and is the first item displayed in response to the threads command. Thus if you are interested in a thread belonging to a threadgroup other than the default (normally the first non-system group), you must first specify the threadgroup via the threadgroup command.

If you wish to list the code where the thread is suspended, you may need to use the thread command and the up command to set the thread context and the stack frame context to the appropriate section of code. See the list command.

resume [thread id(s)]

The resume command with no arguments resumes all suspended threads. resume followed by one or more thread ids resumes those threads only. The thread id argument is the index number of the thread in the current threadgroup; the indices begin at 1 in each threadgroup and is the first item displayed in response to the threads command. Thus if you are interested in a thread belonging to a threadgroup other than the default (normally the first non-system group), you must first specify the threadgroup via the threadgroup command.

threadgroup <name>

If you are interested in a thread in a threadgroup other than the default, you must specify that threadgroup via the threadgroup command. The name argument should be the threadgroup name which is the last item displayed on a line in response to the threadgroups command.

thread <thread id>

You can specify a thread of interest to jdb via the thread command. The thread id is the index number of the thread in the current threadgroup; the index numbers begin at 1 in each threadgroup and are the first item displayed in response to the threads command. Thus if you are interested in a thread belonging to a threadgroup other than the default (normally the first non-system group), you must first specify the threadgroup via the threadgroup command. Once you have specified a thread of interest, you can examine its stack.

Breakpoints

If you want your program to stop so you can examine variables or step through it one line at a time, you must set a breakpoint, which is essentially a stop sign telling the debugger to stop the program when execution reaches that point in the code. You can either instruct jdb to stop at a certain line number or in a method for a class.

stop in <class id>.<method>

This command causes jdb to set a breakpoint at the first executable statement in the method specified.

stop at <class id>:<line>

This command causes jdb to set a breakpoint at the specified line number.

Note that if you have more than one class in your .java file, you must specify the class containing the code when you specify the line number, even if the class name is different than the file name. For example, if the StockTicker example were stored in a file named The StockTicker.java, and if you wanted to stop at line 117, which is the

         tickerWin.repaint(); 

line near the bottom, you must type

         stop at ShiftPainter:117

clear <class id>:<line> and clear <class id>.<method>

These clear commands clear the specified breakpoint.

clear and stop

The clear or stop command with no arguments causes jdb to list the currently set breakpoints.

Controlling Execution

You may want to set breakpoints before executing any lines of code, so jdb does not start execution of your application or applet until you type run. Then, once the program is stopped at a breakpoint, you may want to continue on to the next breakpoint, or execute the next line of code in the current function, or step into any other functions that are called in the current line of code. These commands allow you to control the granularity of program execution.

run <class> [args]

jdb does not execute any class until instructed to run. If you started jdb with a classname as a command line argument, you can issue the run command with no arguments to start execution of that class. When starting a stand-alone application, execution starts at the main method for the class. For applets, execution starts at the init method. If you load the class(es) via a load command, or have not loaded any classes, you must specify the class name as an argument to the run command. The optional args should be the command line arguments for the class being run.

Once the program is stopped at a breakpoint, you can control execution from that point via the step, next, or cont command.

step

When execution is stopped, issuing the step command causes jdb to execute the current line. If the current line contains a call to a method, jdb steps into the method, stopping at the first executable line in the called method.

next

When execution is stopped, issuing the next command causes jdb to execute the current line. If the current line contains a call to a method, jdb executes that method, completes the execution of the current line, and stops at the line following this one in the current method.

cont

Issuing a cont command when execution is stopped causes the program execution to continue normally.

Printing Variable Values

Whenever the application or applet is stopped, you can display the values of local variables or member variables. Note that if you have an object that has other objects as data members, if you ask jdb to print the data member object before it has been created, jdb complains that it is not a valid variable or object. In fact the object may be valid but is simply null because it has not been created yet. Once it is created, an attempt to print it succeeds.

locals

Typing locals at the prompt when the application is stopped causes jdb to display all the local variables within the current scope. Remember that in order for jdb to be able to access any local variables, you must have compiled the classes with the -g option. If the program is not stopped, locals displays an error message.

print <id> [<id>(s)]

Using print, you can print a single value or several values, of any of the local or member variables available within the current scope. The parameter id can be a local variable name, a member variable, a class name followed by . followed by a static class variable, or a Java expression combining these.

For example, part of the class definition of StockTicker is as follows:

class StockTicker extends java.awt.Canvas {
      static      int     MAXLENGTH = 1000;
            String  tickerStr = null;

When this program is stopped in a StockTicker method which sets the value of tickerStr, the command

print tickerStr

results in this output:

this.tickerStr =      PARQ: 8.5

and the command

print StockTicker.MAXLENGTH

results in this output:

"StockTicker" is not a valid field of (StockTicker)0x13a10a8
StockTicker.MAXLENGTH = 1000

The print command calls the object's toString() method to format the output.

If a class is not loaded when you type the print command, you must specify the class's full name, and the class is loaded as a side effect.

dump <id> [<id>(s)]

dump prints the values of all the variables for a class or a class instance if you specify a class name or class instance as the id. dump <className> prints the static class variable values, whereas dump this or dump <classInstanceVariableName> prints the member variable values.

If you specify a variable for id, dump prints some other information about the variable as well as its value (such as, for example, the offset and count for a string variable).

If a class is not loaded when you type the debug command, you must specify the class's full name, and the class is loaded as a side effect.

Examining the Stack

Whenever your program is suspended or stopped at a breakpoint, you can examine the call stack, which is the list of all functions in the current thread which have not yet returned. jdb always has a stack frame context, which means it is focused on a particular line number in a particular function. By default, the context is at level 1, which is the last function that was called. You can move the context up and down the stack and examine variables in any of the functions in the stack.

where [<thread id>] [all]

The where command with no arguments dumps the stack of the current thread (set with the thread command). where all dumps the stack of all threads. where <thread id > dumps the stack of that thread; thread id is the index number of the thread in the current threadgroup. If the thread is suspended or the program is stopped, you can browse the variables with the print and dump commands. If you have moved the stack level context via the up or down command, the where command shows the current level by listing only those frames at the current level and above.

up [n]

The up command with no argument moves the current thread's stack's context up a frame. up n moves the current thread's stack's context up n frames. The stack level is indicated in the number in brackets in the jdb prompt. Once you have moved up the stack, you can then examine variables in that frame.

down [n]

The down command with no argument moves the current thread's stack's context down a frame. down n moves the current thread's stack's context down n frames. The stack level is indicated in the number in brackets in the jdb prompt. Once you have moved down the stack, you can then examine variables in that frame.

Exceptions

When an exception occurs for which there isn't a catch statement anywhere in the program's stack, Java normally dumps an exception trace and exits. When run with jdb, however, the exception is treated as a non-recoverable breakpoint, and jdb stops at the statement which caused the exception. You can at that point examine local and instance variables to determine the cause of the exception.

Note that under jdb a program may throw exceptions that don't occur when run without the debugger. This is because there may be timing differences, especially in the relationships between and among threads. This is not all bad, as these are often caused by not checking for an object's existence or for other initialization. As you add checks for these, your code becomes more robust.

You can specify that jdb catch specific exceptions for debugging via the catch command. When the specified exception is thrown, it is treated as if a breakpoint were set on the instruction which caused the exception. You can remove the exception from the list of exceptions jdb is to catch via the ignore command.

Once jdb stops, either because of an uncaught or a caught exception, in order to list the code at the point the exception occurred, do the following:

Set the jdb thread focus to the thread containing the instruction that caused the exception. To do this type threads, then thread n, where n is the index of the thread wanted. Then type where, to see the stack for that thread. Then type up n, where n is the number of levels to move up the stack to focus the stack context on your code containing the instruction that caused the exception. Then type list.

catch <class id>

The catch command causes jdb to treat the occurrence of the <class id> exception as a breakpoint. catch with no argument lists the exceptions in the catch list. <class id> can be a Java exception or one you have defined. Examples of catch commands are:

catch java.io.IOException
catch StockReader.NoInputException

ignore <class id>

The ignore command causes jdb to remove the <class id> exception from the list of exceptions it breaks for. Note that the ignore command only causes jdb to ignore the specified exception; the Java interpreter continues to catch it. ignore with no argument lists the exceptions in the catch list.

Miscellaneous Commands

These commands are easy to learn and use, but are quite helpful, especially the help command.

gc

gc frees unused objects.

help or ?

help or ? displays the list of recognized commands with a brief description of each.

!!

!! repeats the previous command.

exit or quit

exit or quit exits the jdb debugger.

A Simple Debugging Example

Here are the steps to get you started debugging.

To compile the program with a symbol table appropriate for debugging:

javac -g StockTicker.java

To start the debugging session:

jdb StockTicker

To set breadpoints:

stop at StockTicker:181
stop in ShiftPainter.paint

To start execution:

run

Once the program has stopped at the breakpoint, to list the code surrounding the breakpoint:

list

To list code farther from the breakpoint, surrounding line 150:

list 150

To display the local variables:

locals

To print the value of a particular class instance variable:

print tickerWin.paintX

To dump all member variables of an object:

dump tickerWin

To execute one line of code:

next

To find out what the stack looks like, and what the current stack context is:

where

To move up a level in the stack:

up

To list the code of the method in the new stack context:

list

To continue execution:

cont

To exit jdb:

quit

QUE Home Page

For technical support for our books and software contact support@mcp.com

Copyright ©1996, Que Corporation