In this chapter you'll learn to use the Java debugger to trace and debug the Java programs you develop. You'll learn how to invoke the debugger, load class files, and examine classes as they are executed. You'll also explore the commands provided by the debugger and learn to use them through a hands-on tutorial. When you have finished this chapter, you will know how to use the debugger to analyze, test, and debug your Java programs.
The Java debugger enables Java programmers to debug their programs without having to insert special debugging instructions into their code. The debugger has a number of features, including support for multithreaded programs and remote applications. The debugger is invoked with the
jdb command. To get a quick summary of the commands provided by the debugger, enter the debugger command as follows:
C:\java\jdg>jdb
Initializing jdb...
>
| Note |
The Java debugger has a few bugs of its own. To get the debugger to run properly, you may have to establish an active Internet connection.
|
The debugger takes a few seconds to initialize and then provides you with the debugger prompt. At the debugger prompt, type
help to get a description of the commands it supports:
C:\java\jdg>jdb
Initializing jdb...
> help
** command list **
threads [threadgroup] -- list threads
thread <thread id> -- set default thread
suspend [thread id(s)] -- suspend threads (default: all)
resume [thread id(s)] -- resume threads (default: all)
where [thread id] | all -- dump a thread's stack
threadgroups -- list threadgroups
threadgroup <name> -- set current threadgroup
print <id> [id(s)] -- print object or field
dump <id> [id(s)] -- print all object information
locals -- print all local variables in current stack frame
classes -- list currently known classes
methods <class id> -- list a class's methods
stop in <class id>.<method> -- set a breakpoint in a method
stop at <class id>:<line> -- set a breakpoint at a line
up [n frames] -- move up a thread's stack
down [n frames] -- move down a thread's stack
clear <class id>:<line> -- clear a breakpoint
step -- execute current line
cont -- continue execution from breakpoint
catch <class id> -- break for the specified exception
ignore <class id> -- ignore the specified exception
list [line number] -- print source code
use [source file path] -- display or change the source path
memory -- report memory usage
gc -- free unused objects
load classname -- load Java class to be debugged
run <class> [args] -- start execution of a loaded Java class
!! -- repeat last command
help (or ?) -- list commands
exit (or quit) -- exit debugger
>
Learning the debugger involves learning how to use each of these commands.
In order to get you quickly up to speed on the operation of the debugger, let's use it to analyze a program that you've already developed. Change directories to the
ch06 directory and recompile the
ch06 source files using the
-g option. This will result in additional debugging information being inserted into the compiled bytecode files.
C:\java\jdg\ch06>javac -g CGTextEdit.java
C:\java\jdg\ch06>javac -g CGText.java
C:\java\jdg\ch06>javac -g CGTextPoint.java
C:\java\jdg\ch06>javac -g CGTextBox.java
C:\java\jdg\ch06>javac -g CDrawApp.java
When you have finished compiling the source files, run the debugger by entering the
jdb command:
C:\java\jdg\ch06>jdb
Initializing jdb...
>
At the debugger prompt, type
load jdg.ch06.CDrawApp:
> load jdg.ch06.CDrawApp
0x13a41b8:class(jdg.ch06.CDrawApp)
>
The debugger responds by loading the
CDrawApp class. The hexadecimal number preceding the class name is a Java runtime identifier for the
CDrawApp class. Load the
CDraw class by typing
load jdg.ch06.CDraw:
> load jdg.ch06.CDraw
0x13a54e8:class(jdg.ch06.CDraw)
>
Now that you've loaded these two classes, you want to set a breakpoint in the
main() method of
CDrawApp. A
breakpoint is a place in your program where the debugger stops execution to allow you to enter debugging commands. You set a breakpoint using the
stop in command:
> stop in CDrawApp.main
Breakpoint set in jdg.ch06.CDrawApp.main
>
This tells the debugger to stop execution when it encounters the
main() method of
CDrawApp. Because the
main() method is the first method executed in the
CDrawApp program, the debugger will stop just as it starts to execute
CDrawApp. Run the debugger for
CDrawApp to see how the breakpoint works:
> run CDrawApp
running ...
main[1]
Breakpoint hit: jdg.ch06.CDrawApp.main (CDrawApp:18)
main[1]
The debugger runs
CDrawApp and stops at the breakpoint. It changes its prompt to
main[1] to let you know that it is suspended in the number 1 stack frame of the main thread. A stack frame represents the state of the stack of the Java virtual machine as a result of a method invocation. Refer to the section "JVM Stack" in
Chapter 37, "The Java Virtual Machine." Now that you've stopped the debugger with your breakpoint, use the
list command to see where you are in the program's flow of control:
main[1] list
14 import java.io.IOException;
15
16 class CDrawApp {
17 public static void main(String args[]) throws IOException {
18 => CDraw program = new CDraw();
19 program.run();
20 }
21 }
22
main[1]
The arrow indicates that the debugger has stopped at the point where the program instance of
CDrawApp is about to be created. Now step into the
CDraw() constructor using the
step command. This command allows you to control a program's execution one instruction at a time and is used to produce the following debugger output:
main[1] step
main[1]
Breakpoint hit: jdg.ch06.CDraw.<init> (CDraw:29)
main[1]
The debugger informs you that it has stopped at a breakpoint in the
CDraw constructor. The
<init> identifier is used to indicate a constructor. Enter another
list command to see where you are:
main[1] list
25 static KeyboardInput kbd = new KeyboardInput(System.in);
26 BorderedPrintCGrid grid;
27
28 // Method declarations
29 => CDraw() {
30 grid = new BorderedPrintCGrid();
31 }
32 void run() throws IOException {
33 boolean finished = false;
main[1]
The debugger indicates that you're about to execute the
CDraw() constructor. Skip forward in the program's execution until you reach the
run() method of
CDraw. Now set a breakpoint at the
run() method:
main[1] stop in CDraw.run
Breakpoint set in jdg.ch06.CDraw.run
main[1]
Now continue running the debugger with the
continue command:
main[1] cont
main[1]
Breakpoint hit: jdg.ch06.CDraw.run (CDraw:33)
main[1]
The debugger indicates that it stopped at your breakpoint. Use the
list command to see where the debugger stopped:
main[1] list
29 CDraw() {
30 grid = new BorderedPrintCGrid();
31 }
32 void run() throws IOException {
33 => boolean finished = false;
34 do {
35 char command = getCommand();
36 switch(command){
37 case 'P':
main[1]
You're at the first instruction in the
run() method. Let's take a little break here and look around a bit. First, use the
methods command to list the methods that are available to the
CDraw class:
main[1] methods CDraw
void <init>()
void run()
char getCommand()
void addPoint()
void addBox()
void addText()
void editText()
void editText(CGTextEdit)
void <clinit>()
main[1]
The debugger responds by listing all methods declared for
CDraw, including its constructors,
<init> and
<clinit>. These constructors are internal methods generated by the Java virtual machine. The
classes command lists all classes that are currently known (loaded) by the debugger. Let's take a look at them:
main[1] classes
** classes list **
0x1393008:class(java.lang.Thread)
0x1393018:class(java.lang.Object)
0x1393098:class(java.lang.Class)
0x1393028:class(java.lang.String)
0x1393038:class(java.lang.ThreadDeath)
0x1393048:class(java.lang.Error)
0x1393058:class(java.lang.Throwable)
0x1393068:class(java.lang.Exception)
0x1393078:class(java.lang.RuntimeException)
0x1393088:interface(java.lang.Cloneable)
0x13930b0:class(java.lang.ThreadGroup)
0x13930e0:class(java.lang.System)
0x13930f0:class(java.io.BufferedInputStream)
0x1393100:class(java.io.FilterInputStream)
0x1393110:class(java.io.InputStream)
0x1393128:class(java.io.FileInputStream)
0x1393140:class(java.io.FileDescriptor)
0x1393170:class(java.io.PrintStream)
0x1393180:class(java.io.FilterOutputStream)
0x1393190:class(java.io.OutputStream)
0x13931a8:class(java.io.BufferedOutputStream)
0x13931c0:class(java.io.FileOutputStream)
0x1393208:class(java.lang.StringBuffer)
0x1393240:class(java.lang.Integer)
0x1393250:class(java.lang.Number)
0x13932a8:class(java.lang.NoClassDefFoundError)
0x13932b8:class(java.lang.LinkageError)
0x13932c8:class(java.lang.OutOfMemoryError)
0x13932d8:class(java.lang.VirtualMachineError)
0x13932f0:class(sun.tools.debug.EmptyApp)
0x1393300:class(sun.tools.debug.Agent)
0x1393328:class(java.lang.Runtime)
0x1393370:class(java.util.Properties)
0x1393380:class(java.util.Hashtable)
0x1393390:class(java.util.Dictionary)
0x13933a8:class(java.util.HashtableEntry)
0x1393768:class(java.net.ServerSocket)
0x1393780:class(java.net.PlainSocketImpl)
0x1393790:class(java.net.SocketImpl)
0x13937e0:class(java.net.InetAddress)
0x13938a8:class(java.lang.Math)
0x13938b8:class(java.util.Random)
0x1393948:class(java.lang.Character)
0x1393a18:class(sun.tools.java.ClassPath)
0x1393a28:class(java.lang.Compiler)
0x1393a58:class(java.io.File)
0x1393aa0:class(sun.tools.java.ClassPathEntry)
0x1393b10:class(sun.tools.zip.ZipFile)
0x1393b40:class(java.io.RandomAccessFile)
0x1393bb0:interface(sun.tools.zip.ZipConstants)
0x1393c00:class(sun.tools.zip.ZipEntry)
0x13a2638:class(sun.tools.debug.BreakpointHandler)
0x13a2670:class(sun.tools.debug.BreakpointQueue)
0x13a26a8:class(java.util.Vector)
0x13a26c8:class(java.net.Socket)
0x13a28f0:class(java.io.DataInputStream)
0x13a2910:class(java.net.SocketInputStream)
0x13a2938:class(sun.tools.debug.ResponseStream)
0x13a2950:class(java.net.SocketOutputStream)
0x13a2978:class(java.io.DataOutputStream)
0x13a29e8:class(sun.tools.debug.AgentOutputStream)
0x13a2ab0:class(java.util.HashtableEnumerator)
0x13a2ad8:class(java.util.VectorEnumerator)
0x13a2f48:interface(java.lang.Runnable)
0x13a37d0:interface(sun.tools.debug.AgentConstants)
0x13a3d18:interface(java.io.DataOutput)
0x13a3d28:interface(java.io.DataInput)
0x13a4130:interface(java.util.Enumeration)
0x13a41b8:class(jdg.ch06.CDrawApp)
0x13a44e0:interface(sun.tools.java.Constants)
0x13a4508:class(sun.tools.java.Identifier)
0x13a54e8:class(jdg.ch06.CDraw)
0x13a54f8:class(jdg.ch05.KeyboardInput)
0x13a5810:interface(sun.tools.java.RuntimeConstants)
0x13a6bf8:class(java.lang.ClassNotFoundException)
0x13a6fc8:class(sun.tools.debug.Field)
0x13a7a38:class(sun.tools.debug.BreakpointSet)
0x13a7dc0:class(sun.tools.debug.MainThread)
0x13a8090:class(sun.tools.debug.StackFrame)
0x13a8168:class(sun.tools.java.Package)
0x13a8230:class(sun.tools.java.ClassFile)
0x13a8318:class(sun.tools.debug.LineNumber)
0x13a9830:class(jdg.ch05.BorderedPrintCGrid)
0x13a9840:class(jdg.ch05.PrintCGrid)
0x13a9850:class(jdg.ch05.CGrid)
0x13a9868:class([[C)
0x13a9878:class([C)
0x13a9930:class(jdg.ch05.CGObject)
main[1]
That's quite a number of classes! Look through this list to see if there are any that you recognize. You should be able to identify some classes that are used by the
CDrawApp program. The
threadgroups command lists the threadgroups that are currently defined by the program:
main[1] threadgroups
1. (java.lang.ThreadGroup)0x13930b8 system
2. (java.lang.ThreadGroup)0x13939c0 main
3. (java.lang.ThreadGroup)0x13a7d60 jdg.ch06.CDrawApp.main
main[1]
The three threadgroups are the
system threadgroup (used by the Java runtime system), the default
main threadgroup, and the threadgroup associated with the
CDrawApp program. The
threads command tells you what threads are in a threadgroup:
main[1] threads system
Group system:
1. (java.lang.Thread)0x13931f8 Finalizer thread
2. (java.lang.Thread)0x1393918 Debugger agent
3. (sun.tools.debug.BreakpointHandler)0x13a2640 Breakpoint handler
Group main:
4. (java.lang.Thread)0x13930a0 main suspended
Group jdg.ch06.CDrawApp.main:
5. (sun.tools.debug.MainThread)0x13a7dc8 main at breakpoint
main[1]
When you list the threads in the
system threadgroup, you get a list of all threads maintained by the Java runtime system. The
memory command tells you how much memory is available to the Java runtime system:
main[1] memory
Free: 2439408, total: 3145720
main[1]
The available memory on your computer may differ from mine. For your information, I'm currently running Java on a 486/DX-2 66 computer with 20MB of RAM. Obviously, Java isn't using all of the memory that's available to it. The
where command dumps the stack used by the Java virtual machine. It displays the current list of methods that have been invoked to get you to your breakpoint. An example of the
where command follows:
main[1] where
[1] jdg.ch06.CDraw.run (CDraw:33)
[2] jdg.ch06.CDrawApp.main (CDrawApp:19)
main[1]
The
where command comes in handy when you are deep in the inner layers of several nested method invocations. It shows you how you got to where you are within the program. You can use the
up and
down commands to move up and down the stack. The
up command moves you to a higher stack frame within the stack:
main[1] up main[2] Do a
list command to see the results of the
up command:
main[2] list
15
16 class CDrawApp {
17 public static void main(String args[]) throws IOException {
18 CDraw program = new CDraw();
19 => program.run();
20 }
21 }
22
23 public class CDraw {
main[2]
Now use the
down command to go back down the stack to where you were before:
main[2] down
main[1]
Do another
list command to verify that you have returned to where you were before you entered the
up command:
main[1] list
29 CDraw() {
30 grid = new BorderedPrintCGrid();
31 }
32 void run() throws IOException {
33 => boolean finished = false;
34 do {
35 char command = getCommand();
36 switch(command){
37 case 'P':
main[1]
Now let's look at some variables. Enter the
locals command to get a list of local variables of the
run() method:
main[1] locals
Local variables and arguments:
this = jdg.ch06.CDraw@13a7ce8
finished is not in scope.
command is not in scope.
main[1]
The
finished and
command variables are not in the current scope because they have not yet been declared. Step over to the next statement:
main[1] step
main[1]
Breakpoint hit: jdg.ch06.CDraw.run (CDraw:35)
main[1]
Enter the
list command to see where you have stepped:
main[1] list
31 }
32 void run() throws IOException {
33 boolean finished = false;
34 do {
35 => char command = getCommand();
36 switch(command){
37 case 'P':
38 addPoint();
39 System.out.println();
main[1]
Do another
locals command. The
finished variable should now be in scope:
main[1] locals
Local variables and arguments:
this = jdg.ch06.CDraw@13a7ce8
finished = false
command is not in scope.
main[1]
You have now covered most of the debugger commands. Now let's go on to debugging multithreaded programs. Type
exit to exit the debugger.
The Java debugger supports the debugging of multithreaded programs. In fact, it provides a great tool for understanding how multithreaded programs work. In this section, you use the debugger to debug the
ThreadTest1 program that you developed in
Chapter 8, "Multithreading." Change directories to the
ch08 directory and enter
javac -g ThreadTest1.java to add additional debugging information to the
ThreadTest1.class bytecode file:
C:\java\jdg\ch08>javac -g ThreadTest1.java
C:\java\jdg\ch08>
Now start
jdb and load
ThreadTest1 with the command
jdb ThreadTest1:
C:\java\jdg\ch08>jdb ThreadTest1
Initializing jdb...
0x13a41b8:class(ThreadTest1)
>
Set a breakpoint at the
main() method of
ThreadTest1:
> stop in ThreadTest1.main
Breakpoint set in ThreadTest1.main
>
Run
ThreadTest1:
> run ThreadTest1
running ...
Breakpoint hit: ThreadTest1.main (ThreadTest1:9)
main[1]
The debugger runs
ThreadTest1 and stops at your breakpoint. Do a
list command to see where the debugger stopped:
main[1] list
5 import java.lang.InterruptedException;
6
7 class ThreadTest1 {
8 public static void main(String args[]) {
9 => MyThread thread1 = new MyThread("thread1: ");
10 MyThread thread2 = new MyThread("thread2: ");
11 thread1.start();
12 thread2.start();
13 boolean thread1IsAlive = true;
main[1]
The debugger is at the beginning of the
main() method. It has not created any new threads at this time. Use the
threads command to verify this:
main[1] threads
Group ThreadTest1.main:
1. (sun.tools.debug.MainThread)0x13a5d88 main at breakpoint
main[1]
The only thread is the current main thread of execution. Set a breakpoint to line 11 of
ThreadTest1, the point where both
thread1 and
thread2 will be declared:
main[1] stop at ThreadTest1:11
Breakpoint set at ThreadTest1:11
main[1]
Now jump to that point in the program:
main[1] cont
main[1]
Breakpoint hit: ThreadTest1.main (ThreadTest1:11)
main[1]
Use the
threads command again to see the effect of the
thread1 and
thread2 declarations:
Group ThreadTest1.main:
1. (sun.tools.debug.MainThread)0x13a5d88 main at breakpoint
2. (MyThread)0x13a6b70 thread1: zombie
3. (MyThread)0x13a6b98 thread2: zombie
main[1]
Both
thread1 and
thread2 are in the New Thread state. The debugger refers to them as
zombies. That's a curious term considering that the threads have neither started nor died at this point in the program's execution. Now jump ahead in the program to line 13, where both threads are started. First, set the breakpoint:
main[1] stop at ThreadTest1:13
Breakpoint set at ThreadTest1:13
main[1]
Now jump ahead to the breakpoint:
main[1] cont
Breakpoint hit: ThreadTest1.main (ThreadTest1:13)
main[1]
Let's take a quick look around to make sure you are where you want to be:
main[1] list
9 MyThread thread1 = new MyThread("thread1: ");
10 MyThread thread2 = new MyThread("thread2: ");
11 thread1.start();
12 thread2.start();
13 => boolean thread1IsAlive = true;
14 boolean thread2IsAlive = true;
15 do {
16 if(thread1IsAlive&& !thread1.isAlive()){
17 thread1IsAlive = false;
main[1]
You should now get different results when you execute the
threads command:
Group ThreadTest1.main:
1. (sun.tools.debug.MainThread)0x13a5d88 main at breakpoint
2. (MyThread)0x13a6b70 thread1: suspended
3. (MyThread)0x13a6b98 thread2: running
main[1]
| Note |
Depending on the machine on which you run the jdb, you may find that both threads are suspended when you execute the threads command.
|
The debugger tells us that
thread1 is suspended and
thread2 is running. The
suspend command is used to suspend the execution of a running thread. It takes the number of the thread identified by the
threads command as its argument. The
suspend command is used as follows:
main[1] suspend 3
main[1]
Use the
threads command to verify that it works:
main[1] threads
Group ThreadTest1.main:
1. (sun.tools.debug.MainThread)0x13a5d88 main at breakpoint
2. (MyThread)0x13a6b70 thread1: suspended
3. (MyThread)0x13a6b98 thread2: suspended
main[1]
Now switch threads to
thread1 using the
thread command:
main[1] thread 2
thread1: [1]
Notice how the prompt changed to indicate that you switched to
thread1. Let's do a
list command to see where we are in
thread1. The results of the
list command follow:
thread1: [1] list
36 System.out.println(name+message[i]);
37 }
38 }
39 void randomWait(){
40 => try {
41 sleep((long)(3000*Math.random()));
42 }catch (InterruptedException x){
43 System.out.println("Interrupted!");
44 }
thread1: [1]
Thread1 is in the middle of the
randomWait() method. Switch threads to see what
thread2 is up to:
thread2: [1] list
36 System.out.println(name+message[i]);
37 }
38 }
39 void randomWait(){
40 => try {
41 sleep((long)(3000*Math.random()));
42 }catch (InterruptedException x){
43 System.out.println("Interrupted!");
44 }
thread2: [1]
It looks like
thread2 is in the same state as
thread1. Set a breakpoint for
thread1 and
thread2:
thread2: [1] stop at MyThread:36
Breakpoint set at MyThread:36
thread2: [1] thread 2
thread1: [1] stop at MyThread:36
Breakpoint set at MyThread:36
thread1: [1]
Now continue the execution of
thread1:
thread1: [1] cont
thread1: [1] list
32 public void run() {
33 String name = getName();
34 for(int i=0;i<message.length;++i) {
35 randomWait();
36 => System.out.println(name+message[i]);
37 }
38 }
39 void randomWait(){
40 try {
thread1: [1]
The thread executes up to the breakpoint. You can verify this by running the
threads command:
thread1: [1] threads
Group ThreadTest1.main:
1. (sun.tools.debug.MainThread)0x13a5888 main suspended
2. (MyThread)0x13a59f0 thread1: at breakpoint
3. (MyThread)0x13a5a18 thread2: suspended
thread1: [1]
If you use the
step command,
thread1 becomes suspended and
thread2 reaches the breakpoint:
thread1: [1] step
thread1: [1]
Breakpoint hit: MyThread.run (MyThread:36)
thread2: [1] threads
Group ThreadTest1.main:
1. (sun.tools.debug.MainThread)0x13a5888 main suspended
2. (MyThread)0x13a59f0 thread1: suspended
3. (MyThread)0x13a5a18 thread2: at breakpoint
thread2: [1]
You can use the
print and
dump commands to display the values of the message field of
MyThread:
thread2: [1] print MyThread.message
"MyThread" is not a valid field of (MyThread)0x13a5a18
MyThread.message = 0x13a5958 Object[6] = { Java, is, hot,, }
thread2: [1] dump MyThread.message
"MyThread" is not a valid field of (MyThread)0x13a5a18
MyThread.message = 0x13a5958 Object[6] = { Java, is, hot,, }
thread2: [1]
These commands are somewhat buggy. They complain that the fields are not valid, but they display the values of the fields anyway. At this point, you've covered all the important features of the Java debugger. You can experiment with the debugger to see how the two threads continue their execution. When you are finished, use the
exit command to terminate the debugger.
In this chapter you have learned how to use the Java debugger to step through the execution of a Java program. You have learned how to invoke the debugger, load class files, and examine classes as they are executed. In
Chapter 10, "Automating Software Documentation," you will learn how to use another program contained in the Java toolkit-the Java documentation tool. You'll see how this tool can help you to quickly and easily develop documentation for your Java programs.
No comments:
Post a Comment