Debugging is an art that every successful programmer needs to master sooner rather than later. The sad truth is that program bugs appear almost as soon as you start coding. Although a Java compiler always will detect syntax errors, often code that is syntactically correct is not logically correct. These sorts of problems may require the help of a debugger.
Sun’s Java Development Kit (JDK) includes a debugger called JDB (Java Debugger), a command-line application under which you can execute the programs you need to inspect. With JDB, you can inspect variables while a program is running, set breakpoints to halt the execution of a program at a certain point, and execute code line by line (known as single-stepping). You can see just how the contents of variables change after each statement of program code, and you can precisely trace the execution of a program–even as it branches to other functions or iterates through loops.
JDB can be used to debug graphical programs and applets as well. An important thing to note, though, is that the programs you debug must be compiled with the
-g flag. This causes the Java compiler to insert additional debugging information into the resulting Java class file. (See the Sidebar: Reverse Engineering to Peek at Java Class Source Code for an examination of a command that disassembles Java classes so you can explore the original source code behind it.)A Sample Debugging Session
Listing 1 presents a simple Java program that defines a typical student object. The student can receive money and buy pizza, among other methods. This class must be compiled like this if you want to debug it:
javac -g students.java
-g flag tells the Java compiler to include additional information in the resulting .class file, which aids in the debugging process (see Figure 1). Remember, to recompile your Java class without debugging information when releasing it for others to execute. That way, you minimize the file size of your classes. Plus, this does not adversely affect the execution of the class within the Java Virtual Machine in any way.
|Figure 1: Compiling a Class with the
-g Flag and Executing It Normally
However, you can now run the students program through the debugger and get quite a different scenario by using the following command:
Your IDE will advise you that JDB has initialized and then prompt you as to what to do next. The class does not automatically run, like it does when the Java interpreter executes it. You have 33 commands that you can enter at this prompt. Table 1 lists the most important.
|Displays the commands supported by JDB
|Terminates a debugging session
|Displays both the amount of memory used and the amount of memory available to the Java Virtual Machine
|Repeats the last command
|Loads a class into the debugger
|Starts execution of a class that has been loaded
|Lists classes currently known to JDB
|Displays all information relating to a thread, variable, class, field, or argument
|Displays source code lines (from the original Java source code file)
|Prints all current local variables
|Displays information for a thread, variable, class, field, or argument
|Lists the public methods of the current class
stop in class:method
|Sets a breakpoint at the first bytecode of a specific method in a specific class (Execution will stop as soon as this method is called.)
stop at class:line
|Sets a breakpoint at a line in the class’ source code (Execution will stop as soon as this line is reached.)
|Removes all existing breakpoints
|Clears a specific breakpoint
|Displays the call stack, tracing the execution path from class to class
|Executes the current line of code and stops immediately afterwards
|Executes the current line of code completely and stops again (Method calls are not single-stepped.)
|Continues program execution, running until the next breakpoint or exception, or until the program terminates
Breakpoints: The Most Useful Debugger Feature
The most common debugging technique is placing a breakpoint in a source line where you want to investigate either the state of variables or the visual appearance of the output screen. You might use this technique when you have an if statement that never seems to execute, for example. You might want to see the contents of the variables that are used in the if statement’s condition and find out to what they evaluate.
In fact, a breakpoint is the most useful feature in a debugger like JDB. It causes a running program to pause execution at the point you specify. You can see what the user interface looks like or examine any variables before resuming execution again. This feature is tremendously useful for understanding exactly what is happening within a program.
For example, say you want to know how much money is in each of your students’ bank accounts before they buy any pizza or pay their rent. Looking at your program code, you see the following series of lines:
Student2.StudentNumber = 30;Student2.Name = "Bill";Student2.depositMoney (300);Student1.buyPizza ();Student2.buyPizza ();Student1.payRent ();
To set a breakpoint, you first need to start a JDB session with the class file you’re investigating. Then instruct JDB to set a breakpoint at a specific line of program code. The breakpoint must be placed on a line of actual program code, not a blank line or a line with just a comment. The latter will result in an error.
Student1.buyPizza() is first called on line 59. Although you don’t use line numbers in Java, you need to refer to specific line numbers to tell JDB where to break (pause) execution. Thus, it is helpful if your text editor can tell you the number of a particular line in the file. All modern IDEs have this facility. UNIX and Linux users using vi can press CTRL+G to see the number of the line on which the cursor is resting.
The JDB command to set a breakpoint on line 59 is as follows:
stop at students:59
JDB responds as follows:
Deferring breakpoint students:59.It will be set after the class is loaded.
JDB is telling you that it is going to set a breakpoint where you specified it as soon as it loads the class. It hasn’t loaded the class yet because you haven’t actually begun execution. Start your program with the JDB
run command and JDB will execute each line until it reaches your breakpoint.
As soon as the program reaches the breakpoint, it pauses. You then can enter JDB commands again, giving you a chance to inspect variables or other data elements and check that any output is what you expect (see Figure 2).
|Figure 2: Running Your Program in the Debugger
To see just where the program has stopped, you can use the
list command. It shows a few lines of source code, with an arrow indicating where execution stopped.
To inspect the value of the students’ bank accounts, use the following commands:
print Student1.getBalance ()print Student2.getBalance ()
Try Other JDB Commands
Experiment with more of the commands listed in Table 1. For instance, use
next to step line by line through the program. Use
print Student1.getBalance () again after each
next command to see how student 1’s cash balance changes after method calls. The
locals command displays the value of all local variables simultaneously.
Try setting a breakpoint inside one of the methods in the student class. You can break inside the
payRent() method using the command
stop in student:payRent. Now use the
where command to see the call stack of your program.Effective Debugging as a Software Engineering Skill
And really, that’s all there is to JDB! The debugging facilities in Java development suites like those from IBM and Borland may operate differently (although, the command-line JDB program always will be available), but the principles are exactly the same. Setting breakpoints and stepping through lines of code still are the major components of debugging.
One of the most important phases of software development is making certain that the application performs correctly and executes all the functions in its specifications. In software engineering, preventing program defects is easier than correcting them, and fixing problems during development is better than doing so after the application has been released or deployed. That’s why effective debugging is a skill that every programmer must acquire.
If you can master the concepts in this article, then you can finely monitor execution of your program during a debugging session and understand precisely why the program’s output is the way it is and why program decisions (like branching) are made the way they are.