Debugging in JavaDecember, 1997
by Philippe Kaplan Philippe has worked for Dyade, a French applied research center, since he received his PhD in 1992. He specializes in software tools, and is now working on a graphic toolkit written in pure Java.
A new language like Java means new features, new programming techniques, and . . . a brand new generation of bugs. Articles about Java are now common and help to take advantage of the powerful features of the language. However, I have seen few articles that help debugging in this language, as if the debug techniques remain the same across the different programming languages. Believe me, this is wrong. The usual debug techniques are still valid (actually, it is hard to break developer habits in this area) but new techniques are available to tackle new kind of problems. We can improve our efficiency in debugging by taking advantage of Java features. Debugging with Objects Non-object-oriented programs are structured sets of data and instructions. These are quite straightforward to debug if you know roughly where the bug is located. Just set breakpoints in the guilty area and run within the debugger. When the program hits the breakpoint, it stops, and the context can be studied to understand and discover the bug. Things are different in object-oriented languages like Java. Breakpoints are still set on instructions; however, instructions belong to object instances. So breakpoints stop within all instances, and even if we know within which instance the bug is located, there is no way to track down only this instance. For example, assume we create an instance for each dynamic point of a moving graph and there is a bug in some of the points leading to an unexpected peak in the graph display. We know which ones among hundreds of points are faulty, but not how to trace only them and then detect when they go out of bounds. Threads make things worse. The developer is misled by the source code, which appears basically single-threaded. The developer sees one line of the program, but threads are actually running simultaneously at different points of the same program. The puzzle is real: it is difficult to visualize several threads running at different places and on different instances of the same class. Breakpoints are hit, but by which instance, within which thread? Let's forget about high-level debuggers, because today they are still far from being powerful enough to tackle our problem. How can we debug a threaded object with only println statements in the source code ? Putting println in the class definition comes to mind, but is not enough. It will trace the method called by all instances, and we want to trace only a particular instance. Java documentation reveals a heavier hammer, namely, Runtime.traceMethodCall(), which prints a line for each method call of all objects in all threads. The next section discusses this tracing method. However, this is still maladapted for our purposes. The solution appears in Java 1.1, with the inner classes. When the faulty instance is created, we override on the fly the method to be traced so it prints a message. For example, given the class Test where the method monitor has to be traced only on the instance obj1:
public class Test { We subclass on the fly the suspected instance and override the method to be traced. The obj1 creation becomes:
Test obj1 = new Test(1) { During execution, each time the method is called on the specified object, a message line with the calling thread name, the object, the method name, and its argument are printed out synchronously, e.g.: debug: <main> is calling Test$1@80f0dfd.monitor(foo) Only the suspect instance is traced, and this object still behaves exactly as it did (the trace side effect excepted). The synchronized modifier ensures that the lock is acquired before printing the trace, so no other thread may interleave between the trace printing and the call of the method. Therefore, the message is indeed printed at the right time by the right thread. Without the synchronized modifier, no assumption can be made about the calling thread. Obviously, it doesn't matter when the bug doesn't deal with threads. If the debugger is required, just put a breakpoint at the method of the inner class. The anonymous inner class (AIC) name is automatically generated by appending $n behind the original class name, where n is the number of the AIC in this class (e.g., Test$1). Here is an example of a session under the debugger:
$ jdb Test The debugger is stopped before the call super.monitor(label). Remote Tracing Java runs threads and is network aware. The Java debugger (jdb) is so well integrated it allows remote debugging of Java applications in a separate thread. This is quite a new concept for developers (like me) who come from C and its symbolic debugger. Here is an example of a new debugging trick. Sometimes, when everything else fails, we need the ultimate debugging tool: traceMethodCalls(boolean). This function enables and disables tracing at runtime by toggling a debug switch of the byte code interpreter. Unfortunately, it traces all method calls (i.e., all objects in all threads). So the output is often too verbose to be really workable. We are going to see how to let the user toggle the trace interactively without putting intrusive code in the application. Using this feature exactly when needed will reduce the volume of information produced. The principle is quite simple. Because the trace switch affects all the environment, it can be toggled from a separate thread. Indeed, the debugger thread seems the best candidate because it works beside the application ones. So we run the Java debugger, and inside it we execute a standalone Java application that toggles the trace switch. Compile the file Trace.java that contains:
public class Trace { This standalone application controls only the tracing device in the Java virtual machine. Start the target program, with tracing and remote debugging enabled. java_g enables method tracing. The -debug switch opens an external access to the application that is dedicated to the remote debugger jdb.
$ java_g -debug myProg Start the remote debugger in another window. The password argument must be the one given by the application: $ jdb -host localhost -password 3hszsj Load the trace class (jdb window): > load Trace Proceed with the application. When you need trace, just type in jdb: > run Trace This activates the trace: all method calls are now printed out. To stop tracing, type in jdb again : > run Trace The trace is no longer activated, but the user is free to request the tracing again. Note that the Trace class does not belong to the debugged application. The debugger loads this class by itself and executes it in its own thread on user request. The side effect of the command causes application threads to be traced. The target application does not need any change to be traceable. Could you imagine such a miracle with gdb?
Remote Thread Display We debugged objects; we debugged method calls; now let's tackle threads. Threads are volatile. They appear, they disappear, they run, they wait inside or outside a monitor, they are suspended, and they may be numerous. In a single debugger window, it is hard to track a thread life and even harder to follow the interactions between several threads. This is why I propose a simple package to display all threads and their status at regular intervals. (This package is going to be improved in order to provide more user control over thread execution and priority.) It works like a remote debugger, collecting information about threads on the target application. To install the package threadDebug, download threadDebug.zip<https://www.inria.fr/koala/phk/java/threadDebug.zip>. (The source is available from <https://www.inria.fr/koala/phk/java/threadDebug-src.zip>). You don't need to unzip threadDebug.zip. Just set the CLASSPATH environment variable so that it also includes the zip file. The package works like the remote tracing. The application is started with the -debug option, and the package uses the password given to establish a connection to the application, from another window. So here is a typical session. Start the program, with remote debugging enabled, e.g.,
$ java -debug myProg Note that the program may be an applet:
$ java -debug sun.applet.AppletViewer
foo.html Start the remote thread displayer in another window:
$ java threadDebug.ThreadDisplay -host localhost \ The last argument is optional and sets the automatic refresh time in milliseconds. The default refresh time is one second. Several windows will pop up, one for each thread group that lists the threads of this group and another one for the groups. In addition, each change is printed sequentially on the screen. You should see something like Figure 1 on the facing page. The refresh buttons start a manual refresh in windows, in addition to automatic ones. The tool displays active and inactive threads. Threads in "running" state are highlighted. As the application is going on, the "thread displays" reflect the states of all the threads involved in the application. This tool may help locate a bug by pointing out the threads that run the bug. It may also be useful to detect deadlocks and starvation. Note also that on UNIX systems, the ctrl-backslash key (i.e., signal 3) prints a snapshot of all threads on stdout. The listing is more complete but less readable than the threadDisplay tool. This tool, like the previous one, does not require any change in the target application to be enabled.
Conclusion Java, as a new multipurpose language, incorporates many hi-tech features. The object model together with the multithreading introduces a powerful new way of programming, but also a new class of bugs dealing with concurrent access to shared resources. Fortunately, Java designers, aware of the importance of good debugging tools, have set specialized entry points into the language and the virtual machine to ease debugging. Actually, the official debugger, jdb, is only a user interface to the standard Java debug library. With the ability to access debug functionalities from the language and the flexibility of an object-oriented model, we can create our own debug tools. These tools help to investigate particular situations, like we did here for objects and threads.
|
Need help? Use our Contacts page.
First posted: 3rd December 1997 efc Last changed: 3rd December 1997 efc |
|