Debugging Java Programs using JDB

Exclusive offer: get 50% off this eBook here
Apache MyFaces 1.2 Web Application Development

Apache MyFaces 1.2 Web Application Development — Save 50%

Building next-generation web applications with JSF and Facelets

$26.99    $13.50
by Nataraju Neeluru | June 2010 | Java

In this article by Nataraju Neeluru, we will learn how to debug a Java program using a simple command-line debugging tool called JDB. JDB is one of the several debuggers available for debugging Java programs. It comes as part of the Sun's JDK. JDB is used by a lot of people for debugging purposes, for the main reason that it is very simple to use, lightweight and being a command-line tool, is very fast. Those who are familiar with debugging C programs with gdb, will be more inclined to use JDB for debugging Java programs.

We will cover most of the commonly used and needed JDB commands for debugging Java programs. Nothing much is assumed to read this article, other than some familiarity with Java programming and general concepts of debugging like breakpoint, stepping through the code, examining variables, etc. Beginners may learn quite a few things here, and experts may revise their knowledge.

(For more resources on Java, see here.)

Introduction

JDB is a debugging tool that comes along with the Sun's JDK. The executable exists in JAVA_HOME/bin as 'jdb' on Linux and 'jdb.exe' on Windows (where JAVA_HOME is the root directory of the JDK installation). A few notes about the tools and notation used in this article:

  • We will use 'jdb' on Linux for illustration throughout this article, though the JDB command set is more or less same on all platforms.
  • All the tools (like jdb, java) used in this article are of JDK 5, though most of the material presented here holds true and works in other versions also.
  • '$' is the command prompt on the Linux machine on which the illustration is carried out.
  • We will use 'JDB' to denote the tool in general, and 'jdb' to denote the particular executable in JDK on Linux.
  • JDB commands are explained in a particular sequence. If that sequence is changed, then the output obtained may be different from what is shown in this article.

Throughout this article, we will use the following simple Java program for debugging:

public class A
{
private int x;
private int y;

public A(int a, int b)
{
x = a;
y = b;
}

public static void main(String[] args)
{
System.out.println("Hi, I'm main.. and
I'm going to call f1");
f1();
f2(3, 4);
f3(4, 5);
f4();
f5();
}

public static void f1()
{
System.out.println("I'm f1...");
System.out.println("I'm still f1...");
System.out.println("I'm still f1...");
}

public static int f2(int a, int b)
{
return a + b;
}

public static A f3(int a, int b)
{
A obj = new A(a, b);
obj.reset();
return obj;
}

public static void f4()
{
System.out.println("I'm f4 ");
}

public static void f5()
{
A a = new A(5, 6);

synchronized(a)
{
System.out.println("I'm f5, accessing a's fields " +
a.x + " " + a.y);
}
}

private void reset()
{
x = 0;
y = 0;
}
}

Let us put this code in a file called A.java in the current working directory, compile it using 'javac -g A.java' (note the '-g' option that makes the Java compiler generate some extra debugging information in the class file), and even run it once using 'java A' to see what the output is. Apparently, there is no bug in this program to debug it, but we will see, using JDB, how the control flows through this program. Recall that, this program being a Java program, runs on a Java Virtual Machine (JVM). Before we actually debug the Java program, we need to see that a connection is established between JDB and the JVM on which the Java program is running. Depending on the way JDB connects to the JVM, there are a few ways in which we can use JDB. No matter how the connection is established, once JDB is connected to the JVM, we can use the same set of commands for debugging. The JVM, on which the Java program to be debugged is running, is called the 'debuggee' here.

Establishing the connection between JDB and the JVM

In this section, we will see a few ways of establishing the connection between JDB and the JVM.

  1. JDB launching the JVM:

    In this option, we do not see two separate things as the debugger (JDB) and the debuggee(JVM), but rather we just invoke JDB by giving the initial class (i.e., the class that has the main() method) as an argument, and internally JDB 'launches' the JVM.

    $jdb A
    Initializing jdb ...

    At this point, the JVM is not yet started. We need to give 'run' command at the JDB prompt for the JVM to be started.

  2. JDB connecting to a running JVM:

    In this option, first start the JVM by using a command of the form:

    $java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=6000 A
    Listening for transport dt_socket at address: 6000

    It says that the JVM is listening at port 6000 for a connection. Now, start JDB (in another terminal) as:

    $jdb -attach 6000
    Set uncaught java.lang.Throwable
    Set deferred uncaught java.lang.Throwable
    Initializing jdb ...
    >
    VM Started: No frames on the current call stack

    main[1]

    At this point, JDB is connected to the JVM. It is possible to do remote debugging with JDB. If the JVM is running on machine M1, and we want to run JDB on M2, then we can start JDB on M2 as:

    $jdb -attach M1:6000

  3. JDB listening for a JVM to connect:

    In this option, JDB is started first, with a command of the form:

    $jdb -listen 6000
    Listening at address: adc2180852:6000

    This makes JDB listen at port 6000 for a connection from the JVM. Now, start the JVM (from another terminal) as:

    $java -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=6000 A

    Once the above command is run, we see the following in the JDB terminal:

    Set uncaught java.lang.Throwable
    Set deferred uncaught java.lang.Throwable
    Initializing jdb ...
    >
    VM Started: No frames on the current call stack

    main[1]

    At this point, JDB has accepted the connection from the JVM. Here also, we can make the JVM running on machine M1 connect to a remote JDB running on machine M2, by starting the JVM as:

    $java -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=M2:6000 A

Apache MyFaces 1.2 Web Application Development Building next-generation web applications with JSF and Facelets
Published: March 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

(For more resources on Java, see here.)

Using various JDB commands

This section is the core of this article. Here, we will see how to use various JDB commands, and what those commands do. It serves as a supplement to the JDB 'help'. We will use the option of JDB launching the JVM:

$jdb A
Initializing jdb ...

As mentioned earlier, the JVM is not yet started. We need to give 'run' command at the JDB prompt for the JVM to be started. If we do that now, we will see that, the JVM gets started, runs the main method of class A to its completion, and then JDB exits. All we see is the output of the main method. But that is just not what we wanted to see. So, before giving the 'run' command, we will set a breakpoint in the main method, so that when the program is run, the execution is stopped at the beginning of the main method. We can set the breakpoint using 'stop in' or 'stop at' commands. 'stop in' is used to set the breakpoint at the beginning of a method, whereas 'stop at' is used to set the breakpoint at a given line number. Let's use 'stop in'. We need to give the class name and the method name along with the type of parameters as follows:

> stop in A.main(java.lang.String[])
Deferring breakpoint A.main(java.lang.String[]).
It will be set after the class is loaded.
>

Since the JVM has not yet started and hence the class A is not loaded in the JVM, the breakpoint is not immediately set, but rather it is 'deferred' for now, and when the class A is actually loaded, the breakpoint will be set. Note that 'stop in' (or 'stop at') is one of the very few commands that are allowed by JDB, when the JVM has not started yet. Many other commands require the JVM be running. Now, let's issue the 'run' command.

> run
run A
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint A.main(java.lang.String[])

Breakpoint hit: "thread=main", A.main(), line=14 bci=0
14 System.out.println("Hi, I'm main..
and I'm going to call f1");

main[1]

We see that when the JVM has started and loaded the initial class A, JDB has set the breakpoint that was deferred earlier. JDB then allowed the JVM to proceed to execute until the main method is invoked. At this point, we've reached the breakpoint set earlier, and we can see the first executable line in the method. This is the line that is about to be executed. We also see that the JDB prompt has now changed from '>' to 'main[1]'. It indicates that we are currently debugging the 'main' Java thread. Note that there will be other threads that are automatically started by the JVM. We will see a bit more about threads later. For now, let us continue with the main thread.

To see the source around the current location, we can use 'list' command:

main[1] list
10 }
11
12 public static void main(String[] args)
13 {
14 => System.out.println("Hi, I'm main.. and
I'm going to call f1");
15 f1();
16 f2(3, 4);
17 f3(4, 5);
18 f4();
19 f5();
main[1]

The line to be executed is shown by '=>'. Also, we see 5 lines of source before and after the current line. (Note that JDB will be able to show the source when 'list' command is given, only if the source of the class is in the current working directory (from where JDB is executed). If the source is not in the current working directory, then the path where the source is, should be mentioned by using the '-sourcepath' command line option when JDB is started as: $jdb -sourcepath /x/y/z A).

Let the current line be executed by using 'next' command:

main[1] next
> Hi, I'm main.. and I'm going to call f1

Step completed: "thread=main", A.main(), line=15 bci=8
15 f1();

main[1]

The 'next' command allows one line in the source to be executed, and then stops the execution. If the line has a function call, that call will be completed too. To enter a function when it is called, we have to use 'step' command:

main[1] step
>
Step completed: "thread=main", A.f1(), line=24 bci=0
24 System.out.println("I'm f1...");
main[1] list
20 }
21
22 public static void f1()
23 {
24 => System.out.println("I'm f1...");
25 System.out.println("I'm still f1...");
26 System.out.println("I'm still f1...");
27 }
28
29 public static int f2(int a, int b)
main[1]

We have just entered the method f1(), and we can step through the code by using 'next' command. At any point, if we want to finish the execution of the current function (f1() in this case) and go back to the caller (main() in this case), we can use 'step up' command:

main[1] step up
> I'm f1...
I'm still f1...
I'm still f1...
Step completed: "thread=main", A.main(), line=16 bci=11
16 f2(3, 4);

main[1] list
12 public static void main(String[] args)
13 {
14 System.out.println("Hi, I'm main.. and
I'm going to call f1");
15 f1();
16 => f2(3, 4);
17 f3(4, 5);
18 f4();
19 f5();
20 }
21
main[1]

Now, we are back in main. Let us step in to f2().

main[1] step
>
Step completed: "thread=main", A.f2(), line=31 bci=0
31 return a + b;

Though there is just one source line here, let us spend some time to learn an interesting command. Recall that, the .class file of a class contains (among many other things) the "byte codes" for each method of that class. We can use another JDK tool called 'javap' to see such byte codes of a given .class file. In our example, 'javap -c A' command shows that the method A.f2() has the following byte codes:

public static int f2(int, int);
Code:
0: iload_0
1: iload_1
2: iadd
3: ireturn

One line 'return a + b;' in the Java source file corresponds (in the sense, compiled by the Java compiler) to the above set of byte codes in the .class file. We have seen that, when we use ‘next’ or ‘step’ command, the control goes to the next 'source line' in the same method or in the called method if any, respectively. However, it is also possible that we can go just to the next byte code (rather than the next source line). To do this, we can use 'stepi' command. This command provides the finest granularity of stepping through the code.

main[1] stepi
>
Step completed: "thread=main", A.f2(), line=31 bci=1
31 return a + b;

main[1] stepi
>
Step completed: "thread=main", A.f2(), line=31 bci=2
31 return a + b;

main[1] stepi
>
Step completed: "thread=main", A.f2(), line=31 bci=3
31 return a + b;

main[1]

We executed 'stepi' command thrice but the control is still in the same source line. However, notice the 'bci' value (meant for "byte code index") in the output of each of the 'stepi' commands. It has changed from 0 to 1 to 2 to 3. i.e., first, 'iload_0' byte code at index 0 got executed, and then 'iload_1' byte code at index 1 got executed, and then 'iadd' byte code at index 2 got executed, and now the byte code at index 3 is about to be executed.

At any point, to see what the current call stack is, we can use 'where' command:

main[1] where
[1] A.f2 (A.java:31)
[2] A.main (A.java:16)
main[1]

It shows that we are currently in the method A.f2() called by A.main(). To see the byte code positions also in each method, we can use 'wherei' command:

main[1] wherei
[1] A.f2 (A.java:31), pc = 3
[2] A.main (A.java:16), pc = 13
main[1]

Notice the 'pc' ("Program Counter") value for each method on the stack.

Let us finish f2(), come back to main and then step in to f3():

main[1] step up
>
Step completed: "thread=main", A.main(), line=16 bci=16
16 f2(3, 4);

main[1] next
>
Step completed: "thread=main", A.main(), line=17 bci=17
17 f3(4, 5);

main[1] step
>
Step completed: "thread=main", A.f3(), line=36 bci=0
36 A obj = new A(a, b);

main[1] next
>
Step completed: "thread=main", A.f3(), line=37 bci=10
37 obj.reset();

main[1]

At this point, as dictated by the source, an instance of class A is created and a reference to it is held in the variable 'obj'. We can examine this instance, by using the 'print' command:

main[1] print obj
obj = A@1feca64
main[1] print obj.x
obj.x = 4
main[1] print obj.y
obj.y = 5
main[1]

We see that, printing obj.x and obj.y shows their values, but we don't see them when 'obj' itself is printed. To see the full details of an instance, we can use 'dump' command:

main[1] dump obj
obj = {
x: 4
y: 5
}
main[1]

To see the values of all local variables (including the arguments passed) in a method, we can use 'locals' command:

main[1] locals
Method arguments:
a = 4
b = 5
Local variables:
obj = instance of A(id=325)
main[1]

Note that if the class was not compiled with -g option, we would not be able to see the above outputs.

If we want to see where in the code, a particular field is being accessed or modified, we can use 'watch' command:

main[1] watch A.x
Set watch modification of A.x
main[1]

Tomcat 6 Developer's Guide Build better web applications by learning how a servlet container actually works.
Published: December 2009
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

(For more resources on Java, see here.)

Now, we can let the program to continue execution and we will be able to see where, if any, the field A.x is getting modified. But before doing so, let us know another command called 'monitor'. This command takes another command as an argument, and executes the latter command whenever the program is stopped. The following are a few points at which execution is stopped:

  • when a breakpoint is hit
  • when a field access/modification watch point is hit
  • when next, step, step up, stepi commands are run
  • when a method is entered/exited while tracing of methods is ON.

Let us give 'list' command to 'monitor', and continue the program execution by 'cont' command:

main[1] monitor list
main[1] cont
>
Field (A.x) is 4, will be 0: "thread=main",
A.reset(), line=58 bci=2
58 x = 0;
54 }
55
56 private void reset()
57 {
58 => x = 0;
59 y = 0;
60 }
61 }
62
main[1] main[1]

'cont' command lets the program execute until a breakpoint or a watchpoint is hit, or a method is entered or exited while method tracing is on. In this case, we have hit the watchpoint for the field A.x. So, the execution stops just when A.x is about to be modified. And since we had set the 'list' command in 'monitor', we see that as soon as the program is stopped because of hitting the watchpoint for A.x, the 'list' command is executed automatically, which shows us the source where A.x is being modified (here, x is being set to 0).

If no command is given to 'monitor', then it shows the list of currently set commands under 'monitor' along with their 'monitor numbers'.

main[1] monitor
1: list
main[1]

We can remove a command from the monitor list using 'unmonitor' command with the monitor number:

main[1] unmonitor 1
Unmonitoring 1: list
main[1]

We can remove a watchpoint by using 'unwatch' command:

main[1] unwatch A.x
Removed: watch modification of A.x
main[1]

To see all the breakpoints currently set, we can use 'clear' command:

main[1] clear
Breakpoints set:
breakpoint A.main(java.lang.String[])
main[1]

And, to remove a break point, we have to give the complete breakpoint description:

main[1] clear A.main(java.lang.String[])
Removed: breakpoint A.main(java.lang.String[])
main[1]

So far, we've learnt quite a few most common JDB commands. Now, let us have a look at some advanced commands.

As mentioned earlier, we are currently debugging the 'main' Java thread running in the JVM. To see what all thread groups and threads are there in the JVM at the moment, use 'threadgroups' and 'threads' commands:

main[1] threadgroups
1. (java.lang.ThreadGroup)0x146 system
2. (java.lang.ThreadGroup)0x147 main

It shows that there are two thread groups, namely 'system' and 'main', with 0x146 and 0x147 as their ids, respectively. To see all the threads in all thread groups, we can use 'threads' command:

main[1] threads
Group system:
(java.lang.ref.Reference$ReferenceHandler)0x112 Reference Handler cond. waiting
(java.lang.ref.Finalizer$FinalizerThread)0x111 Finalizer cond. waiting
(java.lang.Thread)0x110 Signal Dispatcher running
Group main:
(java.lang.Thread)0x1 main running
main[1]

Here, we see that the thread group 'system' has three threads, namely Reference Handler, Finalizer and Signal Dispatcher, and the first two are in waiting state, whereas the Signal Dispatcher is in running state. The thread group 'main' has only one thread, namely 'main', which is also in running state, and which is what we've been debugging so far.

We can see what all classes are currently loaded in the JVM by using the 'classes' command:

main[1] classes
** classes list **
A
boolean[]
byte[]
char[]
double[]
float[]
int[]
java.io.BufferedInputStream
java.io.BufferedOutputStream
java.io.BufferedWriter
java.io.Closeable
....
sun.reflect.misc.ReflectUtil
sun.security.action.GetPropertyAction
sun.security.util.Debug
sun.util.PreHashedMap
main[1]

Right at the top, we see that our example class A is loaded. In addition to it, we see a lot of other classes, most of which are the system classes. The output shown above is truncated, since the list is too long.

To see more details about a particular class, we can use 'class' command:

main[1] class java.lang.String
Class: java.lang.String
extends: java.lang.Object
implements: java.io.Serializable
implements: java.lang.Comparable
implements: java.lang.CharSequence
nested: java.lang.String$CaseInsensitiveComparator
main[1]

For a given class that is loaded currently, the 'class' command shows what is the super class, what all interfaces are implemented, what are the nested classes, etc.

To see more internals of a particular class, we can use 'methods' and 'fields' commands:

main[1] methods A
** methods list **
A <init>(int, int)
A main(java.lang.String[])
A f1()
A f2(int, int)
A f3(int, int)
A f4()
A f5()
A reset()
java.lang.Object <init>()
java.lang.Object registerNatives()
java.lang.Object getClass()
java.lang.Object hashCode()
java.lang.Object equals(java.lang.Object)
java.lang.Object clone()
java.lang.Object toString()
java.lang.Object notify()
java.lang.Object notifyAll()
java.lang.Object wait(long)
java.lang.Object wait(long, int)
java.lang.Object wait()
java.lang.Object finalize()
java.lang.Object <clinit>()
main[1]

main[1] fields A
** fields list **
int x
int y
main[1]

We see that the 'methods' command lists all the methods of the given class A, along with their signatures, as well as all the methods inherited from the super class java.lang.Object. Note that <init> method is the constructor. And understandably, 'fields' command shows the names and types of the fields in the class.

We can trace method entry and exit, by using the 'trace methods' command:

main[1] trace methods
main[1] cont
>
Method exited: A.reset "thread=main", A.reset(), line=60 bci=10
60 }

main[1] cont
>
Method exited: A.f3 "thread=main", A.f3(), line=38 bci=15
38 return obj;

main[1] cont
>
Method entered: A.f4 "thread=main", A.f4(), line=43 bci=0
43 System.out.println("I'm f4 ");

main[1] cont
> I'm f4

Method exited: A.f4 "thread=main", A.f4(), line=44 bci=8
44 }

main[1] cont
>
Method entered: A.f5 "thread=main", A.f5(), line=48 bci=0
48 A a = new A(5, 6);
main[1]

As shown above, the execution stops whenever any method is entered or exited. We can stop tracing the method entry and exit by 'untrace methods':

main[1] untrace methods
main[1]

Now, let us continue (by using 'next') in A.f5() until we are in inside the synchronized block as shown below:

main[1] list
48 A a = new A(5, 6);
49
50 synchronized(a)
51 {
52 => System.out.println("I'm f5, accessing a's fields " +
a.x + " " + a.y);
53 }
54 }
55
56 private void reset()
57 {
main[1]

To see the information regarding the monitor associated with an object, we can use 'lock' command:

main[1] lock a
Monitor information for a ("A@6d084b"):
Owned by: main, entry count: 1
No waiters
main[1]

It shows that our current thread (main) has acquired the monitor associated with the instance 'a', and that there are no other threads waiting for this monitor. To see what all monitors are owned by the current thread, and what monitors, if any, it is waiting for, we can use 'threadlocks' command:

main[1] threadlocks
Monitor information for thread main:
Owned monitor: instance of A(id=329)
Not waiting for a monitor
main[1]

To see the stacks of all threads, use 'where all':

main[1] where all
Signal Dispatcher:
Finalizer:
[1] java.lang.Object.wait (native method)
[2] java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:120)
[3] java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:136)
[4] java.lang.ref.Finalizer$FinalizerThread.run (Finalizer.java:159)
Reference Handler:
[1] java.lang.Object.wait (native method)
[2] java.lang.Object.wait (Object.java:474)
[3] java.lang.ref.Reference$ReferenceHandler.run (Reference.java:116)
main:
[1] A.f5 (A.java:52)
[2] A.main (A.java:19)
main[1]

And finally, to exit JDB, use 'exit' or 'quit' command.

Analogy between JDB and GDB

For those who are familiar with 'gdb' debugger for C, the following analogy/equivalence of the commands between JDB and gdb might help:

JDB command gdb command
step s
next n
cont c
stop in/at b
clear info b
step up finish
up/down f
where bt
print/dump p

Debugging in IDEs:

Most of the IDEs available today for the development of Java applications have the debugging functionality built in them. For example, Eclipse has most of the features we have discussed in this article. Jdeveloper has also similar debugging functionality. And then, there is a GUI debugger called Jswat. In all these IDE/GUI based debuggers, there will be appropriate menu items to do things like setting/clearing a breakpoint/watchpoint. And also there will be separate windows for showing the current call stack of the current thread, for showing the local variables of a method, for showing the current source, for accepting any command, etc. Some of such IDE/GUI based debuggers even have some new features that are not available in the command-line debuggers like JDB. For instance, we might be able to set the cursor in the source window at some place, and ask the debugger to continue the execution till the control reaches the cursor. All of this is to simplify the process of debugging, but at the cost of extra processing time. On the other hand, JDB, being a command-line tool, is very fast.

Summary

In this article, we have seen most of the commonly used and needed JDB commands for debugging Java programs. There are a few advanced commands as well. Such commands are not covered here, since the purpose of this article is not to go through every JDB command, but to learn the most commonly needed functionality of JDB.


Further resources on this subject:

About the Author :


Nataraju Neeluru is currently working with Oracle India Pvt. Ltd. He has done his M.Tech (in CSE) from IIT Kanpur. He can be contacted at: mr_nataraj@yahoo.co.in

Books From Packt


Oracle JRockit: The Definitive Guide
Oracle JRockit: The Definitive Guide

RESTful Java Web Services
RESTful Java Web Services

JavaFX 1.2 Application Development Cookbook
JavaFX 1.2 Application Development Cookbook

jBPM Developer Guide
jBPM Developer Guide

Swing Extreme Testing
Swing Extreme Testing

Spring Persistence with Hibernate
Spring Persistence with Hibernate

Flex 3 with Java
Flex 3 with Java

NetBeans Platform 6.9 Developer's Guide
NetBeans Platform 6.9 Developer's Guide


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software