If you are still having problems even after you have ruled out installation
and environment problems and included debugging code, it is time to
use tools to test and analyze your program.
Getting Behind the Seat with jdb
Although there are some very good integrated development environment (IDE)
tools on the market, the JavaTM debugger tool, jdb and
its successors have an important role to play in testing and debugging
programs. Some advantages of jdb over IDE tools are it is
free, it is platform independent (some IDE tools are not),
and it runs as a separate process to the program it is debugging. The
benefit to jdb running as a separate process is
you can attach a debug session to a running program.
The downsides to using jdb are it has only a command-line
interface, and it relies on the same code you are trying to debug. This
means if there is a bug in the Java VM, jdb could break
attempting to diagnose that same bug!
The new JBUG architecture was created to solve these problems
in jdb . JBUG , amongst other things,
provides a debugger helper API in the Java VM called the Java VM Debug
Interface (JVMDI). This helper communicates with the debugging
front end using the Java Debug Wire Protocol (JDWP). The debugging front
end uses the remote Java Debug Interface (JDI) to send and receive
commands over the Java Debug Wire Protocol.
JBug is available for Java 2 platforms, and has a jdb style front
end that you will learn more about later.
Simple jdb Test Drive
Back to the classic jdb tool. Here are some simple steps to analyze
a program using jdb . This first example debugs a program from
application startup. The Remote Debugging
example shows how to connect to a running program.
Start the Session
To begin the debug session, compile the
SimpleJdbTest.java
program with full debugging information using javac
and the -g debug flag as shown. In this example, the
SimpleJdbTest.java program is an application,
but it could just as well be an applet. The procedures
for debugging applications with jdb are the
same for debugging applets once the debug session has started.
javac -g SimpleJdbTest.java
Next, start the jdb tool with the program class
name as a parameter:
jdb SimpleJdbTest
Initializing jdb...
0xad:class(SimpleJdbTest)
To debug an applet in appletviewer use the -debug
parameter as in this example:
$ appletviewer -debug MyApplet.html
Initializing jdb...
0xee2f9808:class(sun.applet.AppletViewer)
>
Setting a Breakpoint and Listing Methods
At this point, the SimpleJdbTest class has only been loaded;
the class constructor has not been called. To make jdb
stop when the program is first instantiated, put a stop, or breakpoint, at
the constructor using the stop in command.
When the breakpoints has been set, instruct jdb to run
your program using the run command as follows:
stop in SimpleJdbTest.<init>
Breakpoint set in SimpleJdbTest.<init>
run
run SimpleJdbTest
running ...
main[1]
Breakpoint hit: SimpleJdbTest.<init>
(SimpleJdbTest:10)
The jdb tool stops at the first line in the constructor. To list
the methods that were called to get to this breakpoint, enter
the where command:
main[1] where
[1] SimpleJdbTest.<init> (SimpleJdbTest:10)
[2] SimpleJdbTest.main (SimpleJdbTest:29)
The numbered method in the list is the last stack frame that
the Java VM has reached. In this case the last stack frame
is the SimpleJdbTest constructor that was called from
SimpleJdbTest main.
Whenever a new method is called, it is placed on this stack list. The Hotspot
technology achieves some of its speed gains by eliminating a new stack
frame when a new method is called. To gain a general appreciation of where
the code has stopped, enter the list command.
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 (){
Locating the Source
If the source to the class file stopped in is not available
on the current path, you can tell jdb to find the source with
the use command by giving it the source directory as a parameter.
In the following example the source is in a subdirectory or folder called
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() {
Looking at a Method
To see what happens in the setup method for
SimpleJdbText ,
use the step command to step through the 4 lines to
get to it.
main[1] step
main[1]
Breakpoint hit: java.awt.Frame.<init> (Frame:222)
But wait a minute! This is now the Frame class constructor! If you
keep stepping you follow the Frame Constructor and not the
SimpleJdbText class. Because SimpleJdbTest
extends the Frame class, the parent constructor, which
in this case is Frame , is called on your behalf.
The step up Command
You could continue stepping and eventually you will
return to the SimpleJdbTest constructor, but to return immediately,
you can use the step up command to go back to the
SimpleJdbTest constructor.
main[1] step up
main[1]
Breakpoint hit: SimpleJdbTest.<init>
(SimpleJdbTest:8)
The next Command
You can also use the next command to get to the
setup method. In this next example, the jdb
tool has approximated that the source line is outside
the constructor when it processed the last step up command. To return
to the constructor, use another step command, and to
get to the setup method, use a next command.
To debug the setup method, you can step
through the setup method.
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)
The stop in Command
Another way to get to the setup method is to use the
stop in SimpleJdbTest.setup command. You can list the source
again to check where you are:
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
The print Command
The first thing the setup method does is create
a Panel p .
If you try to display the value of p with the
print p command, you will find that the value is null .
main[1] print p
p = null
This occurred because the line has not been executed and so field p
has not been assigned a value. You need to step over that assignment operation
with the next command and then use the print p
command again.
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]
Setting Breakpoints on Overloaded Methods
Although stepping through small classes is fast, as a general rule on
larger applications, it is often a lot faster to set breakpoints. This
is partly because jdb has a very simple command set and no
shortcuts, so each command has to be pasted or typed in full.
To set a breakpoint in the Button class, use
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)
The message explains why jdb cannot stop in this method
without more information, but the message is slightly misleading as you
do not need to specify the return type for overloaded methods, you just
need to be explicit about exactly which one of the overloaded methods you
want to stop in. To stop in the
Button constructor that creates this Button ,
use stop in java.awt.Button.<init>java.lang.String) .
The cont Command
To continue the jdb session, use the cont
command. The next time the program creates a Button with a
String as the constructor, jdb stops so
you can examine the output.
main[1] cont
main[1]
Breakpoint hit: java.awt.Button.<init>
(Button:130)
If the Button class had not been recompiled with debug
information as described earlier, you would not see the internal fields
from the print command.
Clearing Breakpoints
To clear this breakpoint and not stop every time a Button is
created use the clear command. This example uses the
clear command with no arguments to display the list of
current breakpoints, and the clear command with the
java.awt.Button:130. argument to clear the
java.awt.Button:130. breakpoint.
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
Displaying Object Details
To display details about an object, use the print
command to call the object's toString method, or use
the dump command to display
the object's fields and values.
This example puts a breakpoint at line 17 and uses
the print and dump commands to print and
dump the first Button object in the array of
Button objects.
The dump command output has been abbreviated.
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
}
Ending the Session
That finishes the simple jdb examples. To terminate the
jdb session, use the quit command:
0xee2f9820:class(SimpleJdbTest)
> quit
Remote Debugging
The jdb tool is an external process debugger, which means
it debugs the program by sending messages to and from a helper inside
the Java VM. This makes it is easy to debug a running program, and
helps you debug a program that interacts with the end user.
A remote debug session from the command-line does not interfere
with the normal operation of the application.
Starting the Session
Before the Java 2 release, the only thing required to enable
remoted debugging was to start the program with the -debug
flag as the first argument, and if the application uses native libraries,
make the library name end in _g . For example,
you would need to copy nativelib.dll to nativelib_g.dll
to debug with that library.
In Java 2, things are a little more complicated. You need to tell the
Java VM where the tools.jar file is by using the
CLASSPATH variable. The tools.jar file
is normally found in the lib directory of the
Java platform installation.
You also need to disable the Just In Time (JIT) compiler if one exists. The
JIT compiler is disabled by setting the java.compiler property to
NONE or to an empty string. Finally, as the -classpath
option overrides any previously set user classpath, you also need to add the
CLASSPATH needed by your application.
Putting all of this
together, here is the command line needed to start a program in
remote debug mode. Put this all on one line and include all the classes
you need on the command line.
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
The output is the agent password (in this case, 4gk5hm ) if the
program was successfully started.
The agent password is supplied when starting jdb
so jdb can find the corresponding application
started in debug mode on that machine.
To start jdb in remote
debug mode, supply a host name, which can be either the machine where the remote
program was started or localhost if you are debugging on the
same machine as the remote program, and the agent password.
jdb -host localhost -password 4gk5hm
Listing Threads
Once inside the jdb session, you can list the currently active
threads with the threads command, and use the
thread <threadnumber> command, for example, thread 7
to select the thread to analyze. Once the thread is selected,
use the where command to see which methods have been
called for this 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)
Listing Source
To list the source, the thread needs to be suspended using the
suspend command. To let this thread continue
use the resume command. The example uses
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
Ending the Session
When you finish debugging this program remotely,
clear any remaining breakpoints before quitting the debug
session. To get a list of remaining breakpoints use the clear
command, and to remove them enter clear class:linenumber as
follows:
main[1] clear
Current breakpoints set:
SimpleJdbTest:10
main[1] clear SimpleJdbTest:10
main[1] quit
Using Auto-Pilot
One little known trick with jdb is the jdb startup
file. jdb automatically looks for a file called jdb.ini
in the user.home directory. If you have multiple projects, it
is a good idea to set a different user.home property for each
project when you start jdb . To start jdb with a
jdb.ini file in the current directory, type the following:
jdb -J-Duser.home=.
The jdb.ini file lets you set up jdb configuration
commands, such as use , without having to enter the details each
time jdb runs. The following example jdb.ini file
starts a jdb session for the FacTest class.
It includes the Java platform sources on the source path list and passes
the parameter 6 to the program. It then runs and stops at line 13,
displays the free memory, and waits for further input.
load FacTest
stop at FacTest:13
use /home/calvin/java:/home/calvin/jdk/src/
run FacTest 6
memory
Here is the output from the jdb.ini file execution:
$ 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]
You might wonder if jdb.ini files can be used to control
an entire jdb session. Unfortunately, commands in a
jdb.ini startup file are executed synchronously, and
jdb does not wait until a breakpoint is reached
before executing the next command. This makes printing variables
awkward. You can add artificial delays with repeated help
commands, but there is still no guarantee the thread will be suspended when
you need it to be.
Creating a Session Log
You can use a little-known jdb feature to obtain a record of your
debug session. The output is similar to what you see when you run
jdb -dbgtrace .
To enable jdb logging, create a file called .agentLog
in the directory where you are running jdb
or java -debug .
In the .agentLog file, put the file name that you want the session
information to be written to on the first line. For example, an
.agentLog file would have these contents:
jdblog
When you next run jdb or java -debug ,
you will see jdb session information as shown below.
You can use this information to retrieve the breakpoint hits and
the commands entered if you need to reproduce this debug session.
---- 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]
[TOP]
|