Chapter 46
Debugging Java Code

by Jordan Olin

One of the hurdles for everyone developing in a new language and execution environment such as Java is learning the appropriate techniques and tools that are available for finding problems in the applications being written (such as "bugs"). Besides providing the standard constructs for creating well-designed, object-oriented applications (inheritance, encapsulation, and polymorphism), Java includes new features such as exceptions and multithreading. These features add a new level of complexity to the debugging process.

The goal of this chapter is to give you a thorough understanding of the debugging facilities that are available with the Sun Java Development Kit (JDK) 1.0.2. As an aid to the JDK, this chapter gives you an in-depth look at the Java Virtual Machine (JVM).

The Architecture of the sun.tools.debug Package

In order to get Java to market as quickly as possible, Sun initially chose not to create a development environment to support the creation of Java applications and applets. Instead, Sun provided features and facilities for developers like us to use in order to create these advanced tools. One of these facilities is the sun.tools.debug package, called the Java Debugger (JDB) API. The API consists of a set of classes that allow the creation of custom debugging aids that may interact directly with an application/applet running within a local or remote instance of the JVM.

The package contains one public interface and 20 public classes that work together to allow you to implement a debugger. The debugging interface is modeled after the client/server architecture. The JVM is on the server that hosts the target application, and your debugger is a client that acts as the interface to control the target application. In order to understand the model, each class of the JDB API is discussed within the following five categories:

The way that your debugger interacts with the running application is through a series of remote classes that act as proxies to the objects in your application. A proxy acts as an intermediary between your debugger and the host JVM. You might think of a proxy as a celebrity with an agent who acts as the public point of contact. You never communicate directly with the celebrity, just with the agent. The agent's sole responsibility is to send messages to the celebrity and relay responses back to the interested party. This is exactly what classes implemented in the sun.tools.debug package do. The proxy model keeps the classes small and gives a clean interface for interacting with the host JVM.


NOTE: Many methods in the JDB API throw the generic Exception exception. This should not be confused with the exceptions that you may or may not want to catch. The exceptions thrown by the API typically represent hard-error situations that occur within the JVM as it is servicing a debugger client request.




Before I get into detail on the classes in the sun.tools.debug package, it might be helpful to see how these classes fit together hierarchically. Figure 46.1 shows the hierarchy (in loose OMT format) for the JDB API classes.

FIG. 46.1
Class hierarchy for the JDB API.

Client-Server Debugger Management

One of the most interesting aspects of the debugging facilities that are built into the JVM is their client-server nature. By using the RemoteDebugger class in conjunction with your implementation of the DebuggerCallback interface, you can literally control all aspects of the JVM as it runs an application/applet on your behalf. The debugger client communicates with the JVM via a private TCP/IP connection using a proprietary and undocumented protocol (for security reasons). This private connection is why the source code for the Debugger API is not available in the JDK.


NOTE: Two more classes can be placed in this category: RemoteClass and RemoteThread. RemoteClass supports breakpoint and exception management, as well as descriptive information on the class structure. RemoteThread supports the control of running threads by manipulating the execution state. As they are subclasses of RemoteObject, you will find them described in detail in the section "Special Types" later in this chapter.




DebuggerCallback
Interface
The DebuggerCallback interface is used to provide a mechanism for the JVM's debugger agent to notify your debugger client when something of note has happened within the target application. These "events" are handled via callback methods that support breakpoints, exceptions, termination, thread death, and console output. The public API for the DebuggerCallback interface is shown in Listing 46.1.

Listing 46.1Public API for the DebuggerCallback Interface

public interface DebuggerCallback {
	public abstract void printToConsole( String text ) throws Exception;
	public abstract void breakpointEvent( RemoteThread t ) throws Exception;
	public abstract void exceptionEvent( RemoteThread t,                 
	String errorText) throws Exception;
	public abstract void threadDeathEvent( RemoteThread t ) throws Exception;
	public abstract void quitEvent() throws Exception;
}

Table 46.1 lists each of the public member methods and what they do.

Table 46.1 The DebuggerCallback Interface Public Member Methods
Name Description
printToConsole Called whenever your target applet sends output to System.out or System.err and when the Debugger Agent in the host VM has messages (especially if you create your RemoteDebugger with the verbose flag set to true).
breakpointEvent Called when a breakpoint has been reached in the target application. t is the thread that was running when the breakpoint was reached.
exceptionEvent Happens when an exception is thrown in the target application. t is the thread that was running when the exception occurred, and errortext contains the message sent with the exception.
threadDeathEvent Signals that thread t has stopped in the target application.
quitEvent Informs you that the target application has ended. This can be a result of calling System.exit(), or by returning from the main thread of the application.


RemoteDebugger
If the DebuggerCallback interface is the eyes and ears of your debugger, then the RemoteDebugger class is the mouth and hands. The RemoteDebugger class is your "proxy" to the control of the JVM instance that is hosting the target application/applet being debugged. To use the RemoteDebugger class, you must first create a class that implements the DebuggerCallback interface. This class becomes an argument to the constructor of a RemoteDebugger instance. (Typically, your debugger's main class would fulfill this requirement.) There are two ways to create a RemoteDebugger instance:

Both run as separate processes and use TCP/IP internally to "talk" to your debugger. Once you have created an instance of RemoteDebugger, you will be in direct control of the target application that you will be debugging. You may then begin to make calls against your RemoteDebugger instance to manipulate your debugging session.


NOTE: Four commands start the JVM using the Sun JDK: java java_g appletviewer appletviewer_g The difference is that the _g versions are compiled with a special debugging code that allows the VM to display special trace messages on the target VM's console output (for example, the messages are not redirected to client.printToConsole). These messages include method and instruction tracing output. The output corresponds to the -tm and -t command line options, respectively.




Figure 46.2 shows that the JVM, RemoteDebugger, and DebuggerCallback are related at execution time.

FIG. 46.2
Relationship between the JVM, RemoteDebugger, and DebuggerCallback.

The public API for the RemoteDebugger class is shown in Listing 46.2.

Listing 46.2Public API for the RemoteDebugger Class

public class RemoteDebugger {
      public RemoteDebugger( String host,
                        	    String password,
                        	    DebuggerCallback client,
                        	    boolean verbose ) throws Exception;
	public RemoteDebugger( String javaArgs,
                        	    DebuggerCallback client,
                        	    boolean verbose ) throws Exception;
	public void addSystemThread()
	public void addSystemThread(thread t);
	public void close();
	public RemoteObject get( Integer id );
	public RemoteClass[] listClasses() throws Exception;
	public RemoteClass findClass( String name ) throws Exception;
	public RemoteThreadGroup[] listThreadGroups( RemoteThreadGroup tg ) throws Exception;
	public void gc( RemoteObject save_list[] ) throws Exception;
	public void trace( boolean traceOn ) throws Exception;
	public void itrace( boolean traceOn ) throws Exception;
	public int totalMemory() throws Exception;
	public int freeMemory() throws Exception;
	public RemoteThreadGroup run(	int argc,
                              String argv[]) throws Exception;
	public String[] listBreakpoints() throws Exception;
	public String[] getExceptionCatchList() throws Exception;
	public String getSourcePath() throws Exception;
	public void setSourcePath( String pathList ) throws Exception;
}

Table 46.2 lists each of the public member methods and what they do.

Table 46.2 The RemoteDebugger Class Public Member Methods
Name Description
RemoteDebugger (String host, String password, DebuggerCallback client, boolean verbose) The first constructor is used to connect to an existing remote JVM. The host argument is the DNS name of the target machine running the JVM. password is part of the security mechanism used to debug remote applications securely. client is your object that implements the DebuggerCallback interface described previously. And, when set to true, verbose causes informational messages to be sent to client.printToConsole() from the host's JVM.
RemoteDebugger (String javaArgs, Debugger Callback client, boolean verbose) The second constructor is very similar to the first, except that it is used to debug Java applications locally. This is probably fine for most GUI applications, but a console application is difficult to debug in this way because the target's output becomes interspersed with the debugger's own informational output. The javaArgs argument should contain valid optional arguments to the java command (excluding the target class). The client and verbose arguments work as mentioned previously.
addSystemThread() Add the calling thread to the list of threads which are not suspended by the debugger. These threads are usually Threads used by the debugger itself.
addSystemThread (Thread t) Add the specific thread t to the list of threads which the debugger will not suspend.
close() Closes down the remote target application/applet and the host JVM.
get(Integer id) Returns a proxy for the object identified by id. The returned RemoteObject instance may be cast to its appropriate type. Use ClassName or instanceof to test its type.
ListClasses() Returns an array of RemoteClass instances that are resident in the host JVM.
FindClasses(String name) Searches the host JVM for a class called name. If the class is not in the VM's class cache, then it is searched for on the target machine. If it is not found, a null is returned. Partial names may be passed in, but there may be ambiguities between user and system classes with the same name.
ListThreadGroups (RemoteThreadGroup tg) Returns an array of RemoteThreadGroup instances for the thread groups that are contained in tg. If tg is null, then all thread groups are returned.
gc(RemoteObject save_list[]) Causes the garbage collector to be run on the host's JVM in order to free all objects that were requested by this debugger instance. Any objects that were sent to the RemoteDebugger are not garbage collected until this call is made, or the debugger exits. save_list is used to prevent any specific objects that are still being examined by this debugger instance from being collected.
trace(boolean traceOn) Toggles the state of the method trace flag in the remote JVM. This command is only valid if you created your RemoteDebugger instance using the constructor that takes javaArgs as the first argument, or if the remote debugging host is one of the _g variants.
itrace(boolean traceOn) Toggles the state of the instruction trace flag in the remote JVM. This command is only valid if you created your RemoteDebugger instance using the constructor that takes javaArgs as the first argument, or if the remote debugging host is one of the _g variants.
TotalMemory() Returns the total amount of memory available for use by the host JVM.
FreeMemory() Returns the amount of memory currently available for use by the host JVM.
run(int argc,String argv[]) Causes the host JVM to load in and run a Java class. argv is an array of Strings that represent the command line to execute. The class name must be the first array element. argc is the number of elements that are valid. The RemoteThreadGroup instance that the class is running in is returned on success, otherwise null is returned.
ListBreakpoints() Returns the list of breakpoints that are currently active in the host JVM. The format of the breakpoint list is either class.method_name or class:line_number.
GetExceptionCatchList() Returns an array of names of the exceptions that the host JVM will send to the debugger as if they are breakpoints.
GetSourcePath() Returns the path string that the host JVM uses when searching for the source files associated with a given class. The format is system-dependent.
setSourcePath(String pathList) Sets the path string that the host JVM uses when searching for the source files associated with a given class.


NOTE: When you start up either java, java_g, appletviewer, or appletviewer_g with the -debug flag, a special password is displayed. This value must be used for the password argument.


NOTE: Remember, the output of the method trace and the instruction trace is displayed on the console of the host VM.

Special Types

The classes in this category represent proxies that give you access to runtime instance data within the host JVM containing your target. These classes are considered special because they are used to represent information on data and control elements other than native types. These proxies allow you to inspect and interact with objects and classes that are loaded in the host JVM. For example, RemoteObject and its subclasses represent objects that your target applet has instantiated.

RemoteValue
RemoteValue is an abstract class that sits at the root of the tree of classes which act as proxies to the remote JVM. This class essentially contains an interface implemented by the classes that follow the interface down the tree. Because the class contains abstract methods, you never explicitly instantiate it; rather, you can assume that any of the subclasses consistently and safely implement RemoteValue's methods. The public API for the RemoteDebugger class is shown in Listing 46.3.

Listing 46.3Public API for the RemoteValue Class

public class RemoteValue 
      implements sun.tools.java.AgentConstants {  // An undocumented                                                    // interface containing static
                                                  // constants used internally                                                    // by RemoteValue
      public String description();
      public static int fromHex( String hexStr );
      public final int getType();
      public final boolean isObject();
      public final boolean isString();
      public static String toHex( int n );
      public abstract String typeName() throws Exception;
}

Table 46.3 lists each of the public member methods and what they do.
Table 46.3 The RemoteValue Class Public Member Methods
Name Description
description() Returns a literal description for this instance of the RemoteValue.
fromHex(String hexStr) Converts the number in hexStr from its hexadecimal representation to an integer value.
getType() Returns the internal numeric identifier for this RemoteValue's type. This value is primarily used internally by the proxy.
isString() Returns true if the RemoteValue is a string.
isObject() Returns true if the RemoteValue instance is an object type versus a native language type (for example, boolean).
toHex(int n) Converts the integer value to its hexadecimal representation in String form.
typeName() Returns the literal type name associated with this instance of RemoteValue.


NOTE: See ShowTypeCodes.java on the CD-ROM for a very simple utility that will display these values.




RemoteField
The RemoteField proxy class is similar to the RemoteValue class, except that it pertains to fields of a RemoteClass or RemoteObject instance. This class provides detailed descriptive information about a field in an object instance or class definition. A field may be any of the following: an instance variable, a static (class) variable, an instance method, or a static (class) method. The public API for the RemoteField class is shown in Listing 46.4.

Listing 46.4Public API for the RemoteField Class

public class RemoteField
       extends sun.tools.java.Field    // An undocumented class containing the                                          // instance variables that hold the 
                                       // representative values of the                                         // RemoteField
implements sun.tools.java.AgentConstants {     // An undocumented interface                                                 // containing static
                                               // constants used internally by                                                 // RemoteField
       public String getModifiers();
       public String getName();
       public String getType();
       public boolean isStatic();
       public String toString();
       public String getTypedName()
}

Table 46.4 lists each of the public member methods and what they do.

Table 46.4 The RemoteField Class Public Member Methods
Name Description
getModifiers() Returns the access modifiers for this RemoteField in literal form (for example, public or private).
getName() The literal field name.
getType() The type of this field as a String (such as int, boolean, or java.lang.String).
isStatic() Returns true if the field is designated as static (see getModifiers(), discussed previously).
toString() Returns the value of this field in String form, as opposed to its native type.
getTypedName() Returns a string which describes the field and its type name.


RemoteObject
RemoteObject is a proxy that allows you to interface with an instance of a class in the host JVM. It is used to access detailed information about the object instance in question, including its class, field information, and field values. The RemoteObject is what you use in order to enumerate through an object instance's data.


CAUTION
You should be aware that as you request RemoteObject (or any subclass) instances from the host JVM, that the host JVM will keep these instances in a nongarbage collected area of memory. Your debugger should either (periodically or on command) call the RemoteDebugger.gc() method in order to release any RemoteObject instances in which you are no longer interested.




The public API for the RemoteObject class is shown in Listing 46.5.

Listing 46.5Public API for the RemoteObject Class

public class RemoteObject
	extends RemoteValue {
	public String description();
	public final RemoteClass getClazz();
	protected void finalize() throws Exception; 
	public RemoteField getField( int slotNum ) throws Exception;
	public RemoteField getField( String name ) throws Exception;
	public RemoteValue getFieldValue( int slotNum ) throws Exception;
	public RemoteValue getFieldValue( String name ) throws Exception;
	public RemoteField[] getFields() throws Exception; 
	public final int getId();
	public String toString();
	public String typeName() throws Exception;
}

Table 46.5 lists each of the public member methods and what they do.

Table 46.5 The RemoteObject Class Public Member Methods
Name Description
description Overrides RemoteValue.description().
getClazz() Returns an instance of RemoteClass that corresponds to this object instance.
getField() This overloaded method returns an instance of RemoteField that is based on either a slot number representing the physical position of this field within the object or the literal name of the field. If the field does not exist (slotNum or name are invalid), then Exception is thrown. If name is not found, the RemoteField instance is returned as a null.
getFieldValue() This overloaded method returns the value of this field as an instance of RemoteValue. The search is based on either a slot number representing the physical position of this field within the object or the literal name of the field. If the field does not exist (slotNum or name are invalid), then Exception is thrown. If name is not found, the RemoteValue instance is returned as a null.
getFields() Returns a list of RemoteField instances representing all of the non-static instance variables defined in this object's class. An exception is thrown if host JVM encounters any problems processing the request.
getId() Returns the instance ID that is used uniquely to identify this object in the host JVM.
toString() Returns a representation of the object in String form. This is completely dependent on the type of object instance being used.
typeName() Overrides RemoteValue.typeName().
finalize Contains code that needs to be executed when this object is collected by the garbage collector.


RemoteClass
RemoteClass represents one of the larger APIs in the sun.tools.debug package and provides details on every aspect of a class definition, including its superclass, fields (static and instance), the interfaces implemented, and methods. As it is a descendent of RemoteObject, remember to gc() the instance at some point when your debugger is finished with it. You can retrieve instances of RemoteClass from RemoteDebugger, RemoteObject, and RemoteStackFrame instances. The public API for the RemoteClass class is shown in Listing 46.6.

Listing 46.6Public API for the RemoteClass Class

public class RemoteClass
	extends RemoteObject {
  // Descriptive methods:
	public String description();
	public RemoteObject getClassLoader() throws Exception;
	public RemoteField getField( int slotNum ) throws Exception;
	public RemoteField getField( String name ) throws Exception;
	public RemoteValue getFieldValue( int slotNum ) throws Exception;
	public RemoteValue getFieldValue( String name ) throws Exception;
	public RemoteField[] getFields() throws Exception;
	public RemoteField getInstanceField( int slotNum ) throws Exception;
	public RemoteField[] getInstanceFields() throws Exception;
	public RemoteClass[] getInterfaces() throws Exception;
	public RemoteField getMethod( String name ) throws Exception;
	public String[] getMethodNames() throws Exception;
	public RemoteField[] getMethods() throws Exception;
	public String getName() throws Exception;
	public RemoteField[] getStaticFields() throws Exception;
	public RemoteClass getSuperclass() throws Exception;
	public String getSourceFileName();
	public boolean isInterface() throws Exception;
	public String toString();
	public String typeName() throws Exception;

	// Debugging management methods:
	public InputStream getSourceFile() throws Exception;
	public void catchExceptions() throws Exception;
	public void ignoreExceptions() throws Exception;
	public String setBreakpointLine( int lineNo ) throws Exception;
	public String setBreakpointMethod( RemoteField method ) throws Exception;
	public String clearBreakpoint( int pcLocation ) throws Exception;
	public String clearBreakpointLine( int lineNo ) throws Exception;
	public String clearBreakpointMethod( RemoteField method ) throws Exception;
	public int[] getLineNumbers() throws Exception
	public int getMethodLineNumber(int index) throws IndexOutOfBoundsException, NoSuchLineNumberException, Exception
	public int getMethodLineNumber(String name) throws NoSuchMethodException, 		 NoSuchLineNumberException, Exception
}

Tables 46.6 and 46.7 list each of the public member methods and what they do.

Table 46.6 The RemoteClass Class Descriptive Public Member Methods
Name Description
description Overrides RemoteObject.description().
getClassLoader() Returns a RemoteObject instance that represents the Class Loader for this class.
getField() This overloaded method returns an instance of RemoteField for a static member based on either a slot number that represents the physical position of this field within the object, or based on the literal name of the field. If the field does not exist (slotNum or name are invalid), then Exception is thrown. If name is not found, the RemoteField instance is returned as a null.
getFieldValue() This overloaded method returns the value of a static field as an instance of RemoteValue. The search is based on either a slot number that represents the physical position of this field within the object, or the literal name of the field. If the field does not exist (slotNum or name are invalid), then Exception is thrown. If name is not found, the RemoteValue instance is returned as a null.
getFields() Overrides RemoteObject.getFields() but returns an array of RemoteField instances that represent all of the static fields available in this class.
getInstanceField() Returns an instance field description as an instance of RemoteField. The search is based on a slot number representing the physical position of this field within the object. If the field does not exist (slotNum invalid), then Exception is thrown. Note that there is no instance data in the field when called in this context.
getInstanceFields() Returns an array of RemoteField instances representing all of the instance fields available in this class. Note that there is no instance data in the field when called in this context.
getInterfaces() Returns an array of RemoteClass instances representing each of the interfaces implemented by this class.
getMethod() Uses name to look up and return, an instance of RemoteField that describes the signature for the specified method.
getMethodNames() Returns a String array containing the names of all methods implemented in this class.
getMethods() Returns an array of RemoteField instances representing all methods implemented in this class.
getName() Returns a String containing the name of this class.
getSourceFileName() Returns the file name that contained the Java source statements used to compile this class. This is just a base name and extension (format is OS dependent) without any path information (for example, MyClass.java).
getStaticFields() Returns an array of RemoteField instances representing all of the static fields available in this class.
getSuperclass() Returns a RemoteClass instance for the superclass of this class. If no superclass was specified (no extends clause was used in the class definition), then an instance of java.lang.Object is returned.
isInterface() Returns true if this RemoteClass instance represents an interface versus a class definition.
toString() Overrides RemoteObject.toString().
typeName() Overrides RemoteObject.typeName().
getLineNumbers Returns the source file line numbers from the class which have code associated with them, in the form of an array.
getMethodLineNumber This method will return the first line of the method which is specified by either a string or the index number.


Table 46.7 The RemoteClass Debugging Management Public Member Methods
Name Description
getSourceFile() Returns an InputStream instance that you may use to display lines from the source file used to create this class (if it is available--a non-null return value). This method is for providing a list type of command in your debugger implementation or for providing interactive source-level debugging. The returned InputStream is typically cast to a DataInputStream prior to use.
catchExceptions() Tells the host JVM to return control to your debugger when an instance of this class is thrown. A ClassCastException is thrown if this class is not a subclass of Exception. This makes the exception appear as a breakpoint to your debugger and causes DebuggerCallback.exceptionEvent() to be called.
ignoreExceptions() Tells the host JVM not to signal your debugger in the case of this exception being thrown. The host JVM still throws the exception, just not to you. A ClassCastException is thrown if this class is not a subclass of Exception. In practice, it is assumed that catchExceptions() has already been called for this class.
setBreakpointLine() Allows your debugger to set a breakpoint based on the source file line number in lineNo. If lineNo is out of range or some other error occurs, a message is returned. Otherwise, an empty string ("") is returned on success. If successful, when your breakpoint is hit, your DebuggerCallback.breakpointEvent() is called.
setBreakpointMethod() Allows your debugger to set a breakpoint based on an instance of RemoteField that contains a reference to a method in this class. The breakpoint is placed on the first line of the method. If for some reason method is invalid, an empty string ("") is returned on success. If successful, your DebuggerCallback.breakpointEvent() is called when your breakpoint is hit.
clearBreakpoint() Your debugger may remove breakpoints using a valid Program Counter (PC) value as specified in pcLocation. If for some reason pcLocation is invalid, an error message is returned. Otherwise, an empty string ("") is returned. This method has limited value as there is no documented method for specifying a breakpoint in this manor.
clearBreakpointLine() Removes a breakpoint that was previously specified for lineNo. If for some reason lineNo is invalid, an error message is returned. Otherwise, an empty string ("") is returned.
clearBreakpointMethod() Removes a breakpoint that was previously specified for method. If for some reason method is invalid, an error message is returned. Otherwise, an empty string ("") is returned.


RemoteArray
In Java, arrays are objects, which applies to how your debugger views arrays. So, you have a special type that is called RemoteArray. This type allows you to interrogate the runtime instance of an array in the host JVM. One of the differences in the RemoteArray class is that there is no way to get directly to its RemoteField information. So, in your debugger, you would use RemoteObject.getField() to get this information. Then you would use RemoteObject.getFieldValue() and cast its return type to RemoteArray to access the actual elements of the array. The public API for the RemoteArray class is shown in Listing 46.7.

Listing 46.7Public API for the RemoteArray Class

public class RemoteArray
	extends RemoteObject {
	public String arrayTypeName( int type );
	public String description();
	public final RemoteValue getElement( int index ) throws Exception;
	public final int getElementType() throws Exception;
	public final RemoteValue[] getElements() throws Exception;
	public final RemoteValue[] getElements( int beginIndex, int endIndex ) 	 throws Exception;
	public final int getSize();
	public String toString();
	public String typeName();
}

Table 46.8 lists each of the public member methods and what they do.

Table 46.8 The RemoteArray Class Public Member Methods
Name Description
arrayTypeName() Returns a string that contains the type of array elements as a String. The type argument is supplied by calling getElementType(), which is defined later. For any type that is a subclass of java.lang.Object, the string Object is returned. You need to use RemoteValue.typeName() on the RemoteValue instance returned by getElement() or getElements() in order to get the actual object class name.
description Overrides RemoteObject.description().
getElement() Returns an instance of RemoteValue containing the value of the array at index. getElement throws an ArrayIndexOutOfBoundsException if index subscripts off the array's boundary.
getElementType() Returns a numeric identifier (defined internally) that represents the data type of the elements contained in the array. Use arrayTypeName(), defined previously, to get the literal associated with this type of designator. Note that for any object type.
getElements() This overloaded method is used to return an array of either all or a subset of RemoteValue instances. If you specify no arguments, then all values are returned. If you specify beginIndex (zero based) and endIndex (maximum of getSize() - 1), then that specific subset of RemoteValue instances is returned. If either index is invalid, then an ArrayIndexOutOfBoundsException is thrown.
getSize() Returns the actual number of elements contained in this array instance.
toString() Overrides RemoteObject.toString().
typeName() Overrides RemoteObject.typeName().


RemoteString
RemoteString is the last of the "special types." It is considered "special" because it is a subclass of RemoteObject, but it is close to being a "native type" because of the way the compiler treats it. This class is very simple, as just about all you can do with a string is to display its contents. The public API for the RemoteString class is shown in Listing 46.8.

Listing 46.8Public API for the RemoteString Class

public class RemoteString

	extends RemoteObject {
	public String description();
	public String toString();
	public String typeName();
}

Table 46.9 lists each of the public member methods and what they do.

Table 46.9 The RemoteString Class Public Member Methods
Name Description
description Overrides RemoteObject.description() and returns the value in the String object or literal null.
toString Overrides RemoteObject.toString() and returns the value in the String object or literal null.
typeName Overrides RemoteObject.typeName().

Native Types

The native types classes are all proxies based on RemoteValue that are used to examine any type of field or stack variable based on a nonobject native type. Native types all share an identical interface to allow complete polymorphic use via the RemoteValue abstract type.

The native types supported by this API are described in Table 46.10.

Table 46.10 ative Types Supported by the RemoteXXX Classes
Native Type RemoteXXX Class
boolean RemoteBoolean
byte RemoteByte
char RemoteChar
double RemoteDouble
float RemoteFloat
int RemoteInt
long RemoteLong
short RemoteShort


The public API shared by the native type classes is shown in Listing 46.9. The XXX portion of the <RemoteXXX> tag may be replaced with any of the native types using proper capitalization (for example, RemoteBoolean for Boolean), as described in Table 46.10.

Listing 46.9Public API for the Set of RemoteXXX Classes

public class <RemoteXXX>
	extends RemoteValue {
	public <native type> get();
	public String toString();
	public String typeName();
}

Table 46.11 lists each of the public member methods and what they do.

Table 46.11 The RemoteXXX Class Public Member Methods
Name Description
get Returns the value contained in the RemoteXXX class as a native value. The <native type> designator as shown in Listing 46.9 may be replaced with any type from the "Native Type" column of Table 46.10 based on the actual RemoteXXX class that the current instance represents. For example, a RemoteBoolean class returns a real boolean value from its get( ) method.
toString Overrides RemoteObject.toString().
typeName Overrides RemoteObject.typeName().

Stack Management

Once you get to a point in your debugger where you can begin to stop execution to examine things, then the stack becomes very important. In the JVM, everything that describes the state of the current method being executed is held in a stack frame. The stack frame includes items such as the method arguments, local variables, program counter (PC), method name, and so on. The RemoteStackFrame represents all of the execution time characteristics of a running Java method and is the proxy to this unit of control from your debugger. The RemoteStackFrame in turn provides you with information on data that is physically resident in this stack frame via RemoteStackVariable instances. These instances are the proxies to the actual variables that are available for the method currently in context. Currently in context can be defined as the stack frame method that is active.

StackFrame
The StackFrame class is very thin and basically used as a place holder to represent a method in a suspended thread in the host JVM. It is used as the superclass for RemoteStackFrame and only includes a constructor that doesn't take arguments and a toString() method for retrieving this object's value in a String. The public API for the StackFrame class is in Listing 46.10.

Listing 46.10Public API for the StackFrame Class

public class StackFrame {
	public StackFrame();
	public String toString();
}

RemoteStackFrame The RemoteStackFrame class is the proxy that allows you to interact with the stack frame of a suspended thread in the host JVM. The RemoteStackFrame instance can basically describe the execution state of itself and enumerate through its variables. Your debugger uses this class in conjunction with the other debugger classes to display the state of the method that is in context. The public API for the RemoteStackFrame class is shown in Listing 46.11.

Listing 46.11Public API for the RemoteStackFrame Class

public class RemoteStackFrame
	extends StackFrame {
	public int getLineNumber();
	public RemoteStackVariable getLocalVariable( String name ) throws Exception;
	public RemoteStackVariable[] getLocalVariables() throws Exception;
	public String getMethodName();
	public int getPC();
	public RemoteClass getRemoteClass();
}

Table 46.12 lists each of the public member methods and what they do.

Table 46.12 The RemoteStackFrame Class Public Member Methods
Name Description
getLineNumber() Returns the line number relative to the beginning of the source file that is associated with the current position of this stack frame in a suspended thread.
getLocalVariable() Returns the RemoteStackVariable instance associated with name in this RemoteStackFrame instance.
getLocalVariables() Returns an array of RemoteStackVariable instances that are available in this RemoteStackFrame instance.
getMethodName() Returns a String containing the name of the method that is represented by this RemoteStackFrame instance.
getPC() Returns the JVM's PC relative to the first bytecode of the method that this RemoteStackFrame instance represents. The PC is like a pointer into the Java bytecode stream and is moved as each bytecode is interpreted by the VM.
getRemoteClass() Returns an instance of RemoteClass that defines the method represented by this RemoteStackFrame instance.


RemoteStackVariable
The RemoteStackVariable class is the proxy that gives you access to the values contained in a RemoteStackFrame instance. The method arguments and the local variables are all returned as RemoteStackVariable instances that hold the state, the identification of the variable, and its current value. The public API for the RemoteStackVariable class is shown in Listing 46.12.

Listing 46.12Public API for the RemoteStackVariable Class

public class RemoteStackVariable
	extends LocalVariable {	// This is a private class that contains the data
	                         // items that are exposed via Remote-			        
                             // StackVariable methods
	public String getName();
	public RemoteValue getValue();
	public boolean inScope();
}

Table 46.13 lists each of the public member methods and what they do.

Table 46.13 The RemoteStackVariable Class Public Member Methods
Name Description
getName Returns a String that contains the literal name of this RemoteStack-Variable instance.
getValue Returns a RemoteValue instance that contains the data value for this variable at this moment. This object may be cast to an appropriate RemoteValue subclass.
inScope Returns true if this RemoteStackVariable instance is currently in scope. A RemoteStackVariable is out of scope if the block that defines it is not in context: For example, a counter defined within a for loop construct or an exception variable defined within a catch statement.

Thread Management

One of the more challenging tasks when writing Java applications is how to debug threads. Fortunately for us Java developers, Sun has included special classes to help debug multi-threaded applications. The thread manager in Java is based on two constructs: a thread group and the threads themselves. Java uses the thread group to help categorize and isolate related sets of independent execution paths, or threads.

The debugging aids for multithreaded applications allow us to examine and control the execution of both thread groups and individual threads. This control and execution is accomplished through the RemoteThreadGroup and RemoteThread classes.

One of the complexities of debugging multithreaded applications is how to manipulate the individual threads that are active. When you set a breakpoint in a given class's method, all threads that cross that execution path will break. What actually happens is that the current thread breaks, and the other threads along the same path suspend. In this situation, you may use the RemoteThreadGroup and RemoteThread class to resume the other related threads while you continue to debug (step, examine, and more) the single thread that you are interested in. Keeping that in mind, the techniques for debugging a multithreaded application are essentially identical to debugging a single-threaded application.

Debugging multithreaded applications can be difficult depending on the logic of how the threads share data. The Java language provides many built-in facilities to allow you to control the concurrency of access that threads have over shared data. The synchronized keyword, the wait() and notify() methods of the Object class, and the sleep() and yield() methods of the Thread class provide features that help you architect your logic so that sharing data is accomplished safely in a threaded application. Debugging facilities can help you identify areas of logic that are missing these concurrency primitives.

The following two topics--RemoteThreadGroup and RemoteThread--describe the proxy classes that allow us to do this manipulation.

RemoteThreadGroup The RemoteThreadGroup class is a proxy to an instance of a real ThreadGroup running in the host JVM. As such, it represents a container that can hold instances of RemoteThread as well as embedded RemoteThreadGroup instances. The interface for RemoteThreadGroup is rather simple and provides the ability to retrieve a list of remote threads and stop the execution of all threads and thread groups that are contained within the current group. The public API for the RemoteThreadGroup class is in Listing 46.13.

Listing 46.13Public API for the RemoteThreadGroup Class

public class RemoteThreadGroup
	extends RemoteObject {
	public String getName() throws Exception;
	public RemoteThread[] listThreads( boolean recurse ) throws Exception;
	public void stop() throws Exception;
}

Table 46.14 lists each of the public member methods and what they do.

Table 46.14 The RemoteThreadGroup Class Public Member Methods
Name Description
getName Returns the name of the current RemoteThreadGroup instance.
listThreads Returns an array of RemoteThread instances that exist in the current RemoteThreadGroup instance. If recurse is set to true, then all embedded RemoteThreadGroups are traversed and their member RemoteThread instances are returned as well.
stop Stops the execution of all threads belonging to this thread group. This is very useful if you are debugging a multithreaded application with many thread instances running on the same execution path. Alternatively, you could use listThreads(), and manually choose the threads to stop.


RemoteThread
The RemoteThread class is at the heart of multithreaded debugging for Java applications. It provides the interface to control the execution of a thread once execution has been stopped. Execution may be stopped by a breakpoint being reached, an explicit call to suspend(), or a call to stop(). Once the thread of execution has been suspended (somehow), you may examine the current state of the thread, single step through the thread, manipulate the stack frame, and examine any variables that are in scope. Implementing the use of RemoteThread in your debugger means that you now have everything you need to manage the execution of remote threads. Because its API is so large, the RemoteThread class can be broken up into three categories:

The public API for the RemoteThread class is in Listing 46.14.

Listing 46.14Public API for the RemoteThread Class

public class RemoteThread
	extends RemoteObject {
	// Basic Thread Control
	public String getName() throws Exception;
	public String getStatus() throws Exception;
	public boolean isSuspended();
	public void resume() throws Exception;
	public void stop() throws Exception;
	public void suspend() throws Exception;

	// Execution path control
	public void cont() throws Exception;
	public void next() throws Exception;
	public void step( boolean skipLine ) throws Exception;

	// Stack frame control
	public void down( int nFrames ) throws Exception;
	public RemoteStackFrame[] dumpStack() throws Exception;
	public RemoteStackFrame getCurrentFrame() throws Exception;
	public int getCurrentFrameIndex();
	public RemoteStackVariable getStackVariable( String name ) throws Exception;
	public RemoteStackVariable[] getStackVariables() throws Exception;
	public void resetCurrentFrameIndex();
	public void setCurrentFrameIndex( int iFrame );
	public void up( int nFrames ) throws Exception;
}

Table 46.15 lists each of the public member methods relating to basic thread control and what they do.

Table 46.15 The RemoteThread Class Public Member Methods for Basic Thread Control
Name Description
getName() Returns a String containing the name of this RemoteThread instance.
getStatus() Returns a String containing the literal status of this RemoteThread instance.
isSuspended() Returns true if this RemoteThread instance is suspended.
resume() Resumes execution of this RemoteThread instance from the current program counter. It is assumed that the thread is currently suspended.
stop() Terminates execution of this RemoteThread instance. You cannot resume execution after you stop the thread, but you can examine the current stack frame.
suspend() Suspends execution of this RemoteThread instance at its current location. This is similar to the thread instance receiving a breakpoint. Once suspended, you may use Program Execution Control methods to step through the thread and the Stack Frame Control methods to examine the variables of the current frame. Execution of the thread may continue upon execution of the resume() method.


Table 46.16 lists each of the public member methods relating to execution path control and what they do.

Table 46.16 The RemoteThread Class Public Member Methods for ExecutionPath Control
Name Description
cont() Resumes the current RemoteThread instance from a breakpoint. If the thread is suspended, use resume() instead of cont().
next() Executes to the next line of source in the current RemoteThread instance and does not "step into" any method calls on the way. That is, it executes any intermediate method calls without giving you the opportunity to stop and examine variables and more. This method throws an IllegalAccessError exception if the thread is not suspended or processing a breakpoint. Also, if there is no line number information for this class, then next operates like the step() method below.
step( ) Executes either the next instruction or goes to the next line if skipLine is true. Executing step(false) at this point puts the PC at the first instruction of evaluateCounter(), where as calling next() executes the call to evaluate-Counter() and leaves the PC pointing to the first instruction of line 3.


NOTE: Unlike next(), step() "steps into" any intermediate method calls that are encountered on the way. For example, if you have the following lines of source (where PC is the current program counter):

     1: myCounter += 1; 
PC 	  2: evaluateCounter( myCounter );
     3: System.out.println( "myCounter: " + myCounter ); 




Table 46.17 lists each of the public member methods relating to stack frame control and what they do.
Table 46.17 The RemoteThread Class Public Member Methods for Stack Frame Control
Name Description
down() Moves the stack frame that is currently in context for this RemoteThread instance down nFrames levels. This is typically used after executing the up() method in order to "walk" back down the call stack frames. This command, when used in conjunction with up(), may be used to help implement an interactive call stack window. If this Remote-Thread instance is not suspended or at a breakpoint, then an IllegalAccessError exception is thrown. Also, if nFrames is too great (for example, you try to go past the bottom frame on the execution stack), an ArrayOutOfBounds exception is thrown.
dumpStack() Returns an array of RemoteStackFrame instances representing the execution stack up to and including the current stack frame. To display the call stack, you can iterate through the array of RemoteStackFrame instances and call its toString() method.
getCurrentFrame() Returns the RemoteStackFrame instance for the current frame.
getCurrentFrameIndex() Returns the index to the current RemoteStackFrame in the execution stack.
getStackVariable() Returns the RemoteStackVariable instance associated with name in the current stack frame. A null instance is returned if name is not found.
getStackVariables() Returns the array of RemoteStackVariable instances that are contained in the current stack frame. These represent both arguments to the method and local variables (whether they are in scope at this point).
resetCurrentFrameIndex() Restores the current stack frame to its state prior to making any calls to up(), down(), or setCurrentFrameIndex().
setCurrentFrameIndex() Establishes the stack frame at level iFrame to be the current stack frame in the host JVM.
up() Moves the stack frame that is currently in context for this RemoteThread instance up nFrames levels. This is typically used after a breakpoint in order to "walk" up the call stack frames. When used in conjunction with down(), this command may be used to help implement an inter- active call stack window. If this RemoteThread instance is not suspended or at a breakpoint, then an IllegalAccessError exception is thrown. Also, if nFrames is too great (for example, you try to go past the top frame on the execution stack), an ArrayOutOfBounds exception is thrown.

Putting It All Together

So, by now you are probably asking yourself, "How can I take advantage of all of this great technology?" First, you can simply use the JDB described in the next section. Or, you might choose to think of JDB as an example application and write your own debugger. In this case, the section provides you with some basic guidelines for using the classes in the sun.tools.debug package.

On the CD-ROM included with this book is a file called DebuggerSkeleton.java. This is a shell for a debugger that is based on the sun.tools.debug package. DebuggerSkeleton.java shows how to get started by implementing the DebuggerCallback interface and instantiating an instance of the RemoteDebugger class.

You can use the following steps as a guide in implementing a custom debugging aid with the JDB API:

  1. Create a base class that implements the DebuggerCallback interface.

  2. Create a set of state-oriented instance variables in your base class to hold items such as your instance of RemoteDebugger, the current thread group, and the current thread (this is the same model used by JDB).

  3. Create an instance of RemoteDebugger using either of the two constructors available. The constructor you choose depends on whether you want to debug remotely, locally, or both.

  4. If you are creating a command line-based debugger, start up a command loop to accept interactive debugging commands. If you are developing a GUI-based debugger, then your command's logic will typically be executed from button or menu events.

  5. You may organize your command processing along the following lines, as shown in Table 46.18.

Table 46.18 Command Processing Organization
Category Description
General These commands handle the general flow of the debugger's control. You can take advantage of the RemoteDebugger class instance to handle these commands. Consider options such as context commands (set the current thread group and thread), memory commands, tracing, and more as potential commands for this category.
Informational These commands display information on the current debugging target. You use instances of RemoteObject, RemoteClass, and RemoteStackFrame to display objects, classes, methods, variables, and source lines.
Breakpoints These commands are used to set/reset breakpoints and exceptions. These may be implemented by the methods in RemoteClass and RemoteThread.
Execution These are the commands that may be used once a breakpoint happens, a thread suspends, or an exception is thrown. You can again use RemoteClass and RemoteThread to process these requests.

Whats Missing?

So, now that you are thoroughly familiar with the JDB API, you are probably wondering what else it could provide. Think about the debuggers that are available for languages such as C and C++. These debuggers offer many of the same facilities for debugging, with one big exception--they actually allow you to modify the target application. The Sun implementation of a debugging API is really a read-only view into the system.

Keeping that in mind, what's on my wish list for this interface? Table 46.19 gives you an idea.

Table 46.19Wish List for the JDB API
Wish Description
Modifying Values This would actually allow you to change the value of fields in objects, arguments to methods, and local variables.
Sending Input You can only receive output from the target application. There is no way to send input to it.
Breakpoints You do not have the ability to set a breakpoint at a specific program counter location within a stack frame.
Data Breakpoints You do not have the ability of setting a breakpoint on any kind of data item. Setting a breakpoint could be accomplished manually by controlling everything in the context of a breakpoint handler, but this would be very slow.
Local Trace The trace output should be able to be redirected to the DebuggerCallback interface.
Bytecodes You should be able to get the Java bytecode disassembly off of a RemoteStackFrame instance.


The previous tables list the areas that allow you to make a complete debugging environment. I'm sure there are others. In time, hopefully, Sun will help us all out.

What About Microsofts Java Implementation?

One final point: The information in this section is very specific to the Sun JDK. Microsoft is in the final stages of implementing the JVM into both the Internet Explorer 3.0 and the next release of Windows OS. There are two major changes:

The JDB API from Microsoft is loosely modeled after the sun.tools.debug package but has a slightly different naming convention. Also, the JDB API supports some of the missing features of the Sun package noted previously.


NOTE: Be aware that the following information is close to final but is not officially released. Implementation details may have changed between now and the time that Microsoft ships these new features.




Table 46.20 shows the relationship between the Microsoft debugging classes and the sun.tools.debug package.

Table 46.20 Differences Between sun.tools.debug and the Microsoft Debug Classes
sun.tools.debug Microsoft COM Debug
DebuggerCallback IRemoteDebugManagerCallback
IRemoteProcessCallback
RemoteDebugger IRemoteDebugManager
IRemoteProcess
IEnumRemoteProcess
RemoteValue IRemoteDataField
IRemoteContainerField
RemoteField IRemoteField
IEnumRemoteField
RemoteOject IRemoteObject
IEnumRemoteObject
RemoteClass IRemoteContainerObject
IRemoteClassField
RemoteArray IRemoteArrayField
RemoteString IRemoteStringObject
RemoteBoolean IRemoteBooleanObject
RemoteByte IRemoteByteObject
RemoteChar IRemoteCharObject
RemoteDouble IRemoteDoubleObject
RemoteFloat IRemoteFloatObject
RemoteInt IRemoteIntObject
RemoteLong IRemoteLongObject
RemoteShort IRemoteShortObject
StackFrame
RemoteStackFrame IRemoteStackFrame
RemoteStackVariable IRemoteMethodField
RemoteThreadGroup IRemoteThreadGroup
IEnumRemoteThreadGroup
RemoteThread IRemoteThread
IEnumRemoteThread

The JDB in Depth

Now that you have a good understanding of the underlying debugging facilities in the JDK, JDB can be examined. JDB really serves two purposes:

Our discussion of JDB covers all of the commands in detail and describes the major portions of the JDB API as they are used.

Basic Architecture

As an application, JDB is patterned after the DBX debugger found on many UNIX systems. This is a command line-oriented debugger that allows you to interact with a running application by entering English-like commands for examining and controlling its execution state. These commands allow you to examine variables, set breakpoints, control threads, and query the host JVM about the classes that it has loaded. You may also have the host JVM load classes for you in advance so that you can set breakpoints in methods prior to their execution.

In order to understand fully the architecture of JDB, it may be helpful to print out or have access to its source while you are reading this section. When you install the JDK, there is a relatively small ZIP file in the installed JDK root directory (typically, \ JAVA in theWindows versions) called SRC.ZIP. This file contains the Java source files for all of the publicly documented Java classes, including the class that is the foundation for JDB. The SRC.ZIP file must be unzipped with the Use Directory Names option in order to preserve the source tree. The source tree follows the package-naming convention used in the JDK.

So, if you unzip the SRC.ZIP file into a directory under \JAVA called SRC, you would find two subdirectories under \JAVA\SRC called \JAVA\SRC\JAVA and \JAVA\SRC\SUN. These represent the Java source files in the java.* and sun.* collection of packages, respectively.

The source to JDB is based on a class called TTY, or sun.tools.ttydebug.TTY. The source file (assuming the directory structure above) would be in \JAVA\SRC\SUN\TOOLS\ TTYDEBUG\TTY.JAVA.

As you look at TTY.java, the first thing you will probably notice is that TTY is a simple class that derives from Object, as all classes with no extends clause do. But, it does implement the DebuggerCallback interface as I mentioned previously in the section "Putting It All Together."

You should note that there are a few instance variables to help support application currency. Specifically, a reference to a RemoteDebugger instance (debugger), a Remote ThreadGroup (currentThreadGroup), and a RemoteThread (currentThread) are needed in order to maintain context within the currently executing application. This helps when implementing most of the commands that query for information from a suspended thread and method. After a few private methods, you will see the methods defined in DebuggerCallback, allowing TTY to complete its contract by implementing this interface.

It is now probably easier to jump to the bottom of the source and see its real structure. It starts with a main method that will be called first when TTY.class is loaded and run. This main essentially parses, collects, and verifies all of the command-line arguments and, if everything looks good, creates an instance of the TTY class. The rest of the processing takes off from TTY's custom constructor.

The only constructor in TTY takes seven arguments; these arguments specify the following:

Once in the constructor, the remote debugger instance is created and, if specified, the initial class is loaded. Creating an instance of RemoteDebugger actually causes a JVM to be started for you in the remote system (even if no starting class is specified). After that, a check is made for an input command file. Finally, the command processing loop is started.

The command loop's real functionality lies in a method called executeCommand that expects a tokenized version of the input command line. executeCommand is simply a series of cascading if-else-if statements that check to see if the first token of the input argument matches one of the predefined sets of commands supported by JDB. If so, then the helper method associated with that command is executed. Otherwise, an appropriate error message is displayed ("Huh? Try help...").

So, now that you have a feel for the general structure of JDB and its source (the TTY class), look at the actual command-line arguments and commands supported by JDB.

The JDB Command Line

In order to start JDB in its simplest form, you can type jdb <Enter> at your command prompt. JDB then performs its initialization and presents you with a > prompt. In fact, three "formal" command lines start JDB in various modes, as follows:

  1. Start JDB and create a VM instance on this machine with no class loaded:

    JDB [-dbgtrace] [<java-args>]

    Option Meaning
    -dbgtrace If specified, enables verbose messages to be returned from the JVM instance that is created in order to run the target Java application/applet. These messages are sent to the printToConsole callback method and have the format [debugger: <some message>]. That way, you can filter them in your printToConsole implementation and send them to a log file or window, for example.
    <java-args> This is an optional subset of the arguments that you can specify when running the java command to start an instance of the JVM. The currently recognized options are the following: -cs, -checksource, -noasyncgc, -prof, -v, -verbose,-verify, -noverify, -verifyremote, -verbosegc, -ms, -mx, -ss, -oss, -D, and -classpath.

  2. Start JDB and create a VM instance on this machine with <classname> loaded:

    JDB [-dbgtrace] [<java-args>] <classname> [<args>]

    Option Meaning
    -dbgtrace Same as above.
    <java-args> Same as above.
    <classname> This mandatory argument is the .class file to load initially into the JVM and control by the debugger. Use the run command to begin execution.
    <args> This argument represents any arguments needed by <classname>. It must be specified here, as there is no way to set up arguments for <classname>'s main method after JDB starts up.

  3. Start JDB and connect to a remote VM instance that is already running a class:

    JDB [-dbgtrace] [-host <hostname>] -password <password>

    Option Meaning
    -dbgtrace Same as above.
    -host <hostname> This optional argument specifies a DNS name or IP address of a computer running the host JVM for the Java application/applet that you will be debugging. If this argument is not specified, then localhost is automatically assumed.
    -password <password> This mandatory argument is the password that was displayed on the console when the JVM hosting the application/applet to be debugged was loaded. It is generated by either java, java_g, appletviewer, or appletviewer_g when the -debug flag is specified on the respective command lines.


    Once you enter one of the JDB command lines, the initialization process described previously takes over and, eventually, the interactive prompt (>) appears.

JDB Input Files

If you choose to run a debug session repeatedly with a specific set of commands, you can create a command input file for JDB to use automatically. The command file is a simple ASCII text file where each line contains a valid JDB command followed by the line delimiter that is appropriate to the OS you are running JDB on (for example, CR/LF for Wintel). JDB looks in the following three places, in this order, for a command input file (see Table 46.21).

Table 46.21 Locations for the JDB Command Input File
Directory (System Property) File Name Example
USER.HOME JDB.INI C:\JAVA\JDB.INI
USER.HOME JDBRC /JAVA/.JDBRC
USER.DIR STARTUP.JDB ./STARTUP.JDB


If one of the aforementioned files is found, JDB reads each line and processes the command as if it were typed in at the console. If you want JDB to exit quietly when it has finished processing the file, place a quit or exit command as the last line in the command file. Otherwise, you are left at the JDB prompt with any output from the processed commands visible on the console window. Also, because the calls made to printToConsole are sent to System.out, you may redirect the results of these commands to an output file using command line redirection.

The JDB Command Set

Now that you know how to start JDB, it is useful to know how to operate it as well. In order to show some of the features of the JDB command set, I use a small, threaded application where each thread increments a shared counter and displays the value. Listing 46.15 contains the application.

Listing 46.15MTTest.javaSample Buggy Application

public class MTTest extends Thread {
	static int count = 0;
	static Object sema = new Object();

	public MTTest( ThreadGroup theGroup, String threadName ) {
		super( theGroup, threadName );
	}

	public void run() {
		String	myName = getName();
		while (true) {
			synchronized (sema) {if (count < 30) {
					System.out.println( myName + ": " + count );
					count += 1;
			} else break;
			}
			yield();
		}
		System.out.println( "Exiting " + getName() );
	}

	public static void main( String[] args ) {
		ThreadGroup	theGroup;		// To contain the threads
		MTTest[]	theThreads;		// Array of threads
		// Wait for the user to hit <Enter> to start
		System.out.print( "MTTest: Press the <Enter> key to begin." );
		System.out.flush();
		try { System.in.read(); } 
		catch (java.io.IOException e) {}
		System.out.println("");
		// Create the thread group
		theGroup = new ThreadGroup( "MTThreads" );
		// Create the thread array
		theThreads = new MTTest[3];
		// Create and start the threads
		for (int i = 0; i < theThreads.length ; ++i) {
			theThreads[i] = new MTTest( theGroup, "T" + 						 Integer.toString(i));
			if (theThreads[i] != null)
				theThreads[i].start();
		}
	}
}

One of the first things to know about debugging anything that is multithreaded with JDB is that it is best to have the target application wait until you are ready and run it in a separate process space. Running in a separate process space prevents the target application's output from getting interspersed with the debugger output. Also, having the application wait for you means that it won't start running as soon as you start up the JVM that will run your application.

In order to follow along with the examples associated with each command, complete the following steps:

  1. For this example, I used the following command line to compile MTTest with debug information (line numbers and local variable information):

    javac -g MTTest.java

  2. I then opened up two command windows: one for the JVM to run MTTest and the other to run JDB within. From the first command window, I started up the JVM as follows:

    java_g -debug MTTest

  3. I used java_g because it supports extended tracing options. The -debug option told the JVM that I was going to communicate with it via external proxies, and MTTest is the name of the class file to load. Once java_g is started, it displays the following information on the system console:

    Agent password=xxxxxx

    where xxxxxx is the password to use when starting up JDB, which is the next step.

  4. In the second command window, I entered the following command to start JDB and connect to the running JVM:

    jdb -host localhost -password xxxxxx

  5. To put the debugging session at an interesting point, enter the following in your JDB command window (you are setting a breakpoint at the start of the run method of MTTest. Don't worry about what the commands mean--I get to them in a future section.):

    >stop in MTTest.run
    Breakpoint set in MTTest.run

  6. Now, in the console window that is actually running MTTest (see step 2), press Enter to let MTTest start to execute. You should almost immediately hit a breakpoint and see the following in the JDB console window:

    Breakpoint hit: MTTest.run (MTTest:12)
    T0[1]


Now that everything is ready to go, the specific commands that are implemented by JDB can be examined. For clarity, I have broken the commands into groupings based on their functionality. By using these categories, you can put each command into its respective slot (see Table 46.22).

Table 46.22 JDB Commands by Group
General Context Information Breakpoint Exception Threads
help/? load classes stop catch suspend
exit/quit run dump clear ignore resume
memory threadgroup list step kill
gc thread locals next up
itrace use methods cont down
trace print
!! threadgroups
threads
where


NOTE: kill and next, itrace, and trace are undocumented but implemented commands.


The rest of this section describes each command and its function.

General Commands

These are the commands that are used to control some of the features of the debugger or interrogate the state of the remote JVM.

help
/?
Syntax: help [or ?]

This command displays a list of the "documented" commands that are supported by JDB.

exit
/quit
Syntax: exit [ or quit ]
Uses: RemoteDebugger.close()

This command terminates your debugging session and JDB. The connection between JDB and the remote JVM is broken. If debugging locally, the VM is shut down.

memory
Syntax: memory
Uses: RemoteDebugger.freeMemeory() and RemoteDebugger.totalMemory()

This command displays the total amount of used and free memory in the remote JVM. For example, on my system, the memory command displays the following:

Free: 2674104, total: 3145720\

gc Syntax: gc Uses: RemoteDebugger.gc()

This command causes the garbage collection task to run on the remote JVM. The classes that are not in use by the debugger are freed. JDB automatically tells the JVM not to garbage collect the classes involved with the current RemoteThreadGroup and RemoteThread instances. If you are having a long debug session, then you should use the gc command to occasionally remove the RemoteClass instances that have been cached on your behalf by the remote JVM.

itrace (an Undocumented Command) Syntax: itrace on | off
Use: RemoteDebugger.itrace()

This command enables (on) or disables (off) bytecode instruction tracing on the remote JVM that is hosting your application. The output is sent to System.out on the remote JVM and cannot be intercepted from within your debugging session.

The following is sample output from an itrace:

1393B58   6B4AD8        ifeq goto   6B4ADD (taken)
1393B58   6B4ADD        aload_0 => java.net.SocketInputStream@139EE80/ 1481298
1393B58   6B4ADE        aload_1 => byte[][2048]
1393B58   6B4ADF        iload_2 => 0
1393B58   6B4AE0        iload_3 => 2048
1393B58   6B4AE1        invokenonvirtual_quick 
java/net/SocketInputStream.socketRead([BII)I (4)

trace (an Undocumented Command) Syntax: trace on | off
Use: RemoteDebugger.trace()

This method enables (on) or disables (off) method call tracing on the remote JVM that is hosting your application. The output is sent to System.out on the remote JVM and cannot be intercepted from within your debugging session.

The following is sample output from a trace:

# Debugger agent [ 3] | | | < java/lang/Runtime.traceMethodCalls(Z)V returning
# Debugger agent [ 2] | | < sun/tools/debug/Agent.handle(ILjava/io/DataInputStream;
Ljava/io/DataOutputStream;)V returning
# Debugger agent [ 2] | | > java/io/DataOutputStream.flush()V (1) entered
# Debugger agent [ 3] | | | > java/io/BufferedOutputStream.flush()V (1) entered
# Debugger agent [ 4] | | | | > java/net/SocketOutputStream.write([BII)V (4) entered
# Debugger agent [ 5] | | | | | > java/net/SocketOutputStream.socketWrite([BII)V (4) entered
# Debugger agent [ 5] | | | | | < java/net/SocketOutputStream.socketWrite([BII)V returning
# Debugger agent [ 4] | | | | < java/net/SocketOutputStream.write([BII)V returning
# Debugger agent [ 4] | | | | > java/io/OutputStream.flush()V (1) entered
# Debugger agent [ 4] | | | | < java/io/OutputStream.flush()V returning
# Debugger agent [ 3] | | | < java/io/BufferedOutputStream.flush()V returning
# Debugger agent [ 2] | | < java/io/DataOutputStream.flush()V returning
# Debugger agent [ 2] | | > java/io/FilterInputStream.read()I (1) entered
# Debugger agent [ 3] | | | > java/io/BufferedInputStream.read()I (1) entered
# Debugger agent [ 4] | | | | > java/io/BufferedInputStream.fill()V (1) entered
# Debugger agent [ 5] | | | | | > java/net/SocketInputStream.read([BII)I (4) entered
# Debugger agent [ 6] | | | | | | > java/net/SocketInputStream.socketRead([BII)I (4) entered

The format of the call information uses the signature described in the section on the class file, which is described in a subsequent section on the .class file structure.

!!
(Repeat Last Command)
Syntax: !!

This command re-executes, or repeats, the last entered command. It is not something that is implemented by any remote class; rather, this command is just a feature that is enabled by the JDB command processor.

Context Commands

These commands are used to establish context for the debugging session. They set up the state of the remote JVM and the instance variables used operationally by TTY. In order to use just about any of the commands in JDB, the current thread group and current thread must be set. The initial context is set automatically when you use the run command; otherwise, you must manually set it using the threadgroup and thread commands.

load Syntax: load <classname>
Use: RemoteDebugger.findClass()

load causes the remote JVM to search for and load <classname>. If you do not fully qualify the name of the class to load, the VM tries to look in well-known packages to complete the name. If the class is not found, an error message is returned. Also, an error message is displayed if no <classname> is provided. This command does not affect the current context.

run
Syntax: run [ <classname> [args] ]
Uses: RemoteClass.getQualifiedName and RemoteDebugger.run()

This command loads and begins execution of <classname> or the last <classname> specified on the previous call to the run command. Error messages are returned if the class can't be found or if there is a general failure in attempting to start <classname>. This command also sets the context by establishing initial values for the currentThreadGroup and currentThread.

threadgroup
Syntax: threadgroup <thread group name>
Uses: RemoteDebugger.listThreadGroups and RemoteThreadGroup.getName

This command establishes <thread group name> as the default thread group by putting a reference to its RemoteThreadGroup instance in the currentThreadGroup instance variable. This command is required for using any of the commands that are relating to breakpoints, exception, and thread management. For example, you could enter:

>threadgroup MTThreads

to specify the current default thread group.

thread Syntax: thread t@<thread id> | <thread id>, where <thread id> is an integer constant representing a thread's ID number. (See the threads command.)

Use: RemoteThreadGroup.listThreads

This command sets <thread id> as the current thread in context relative to the current thread group by putting a reference to its RemoteThread instance in the currentThread instance variable. This command is required for using any of the commands relating to breakpoints, exception, and thread management. It is typically used in conjunction with the threadgroup command. For example, you could enter:

>thread 5
T0[1]

to specify the current default thread. T0[1] is now the new prompt showing you that your context is in thread T0, which is the first thread in the current thread group.

use
Syntax: use [source file path]
Uses: RemoteDebugger.getSourcePath and RemoteDebugger.setSourcePath

This command is used to display or set the path that the remote JVM uses to find .class and .java files. If called without any arguments, then the current source file path is displayed. If called with a path (formatted like the classpath system property), then the source file path is updated accordingly. For example, to display the current class/source path and then change it:

>use
.;c:\java\lib\classes.zip
>use .;c:\java\lib\classes.zip;c:\java\lib\classdmp.zip
>use
.;c:\java\lib\classes.zip;c:\java\lib\classdmp.zip

Information Commands

These commands are used to display information about the classes that are currently loaded and known to the remote JVM. They are list-oriented in nature and depend on the context being established as described previously.

classes Syntax: classes
Uses: RemoteDebugger.listClasses and RemoteClass.description

This command displays the class and interface names that are currently known to the remote JVM hosting the debugging target. If this list is unusually large, try running the gc command to free instances of RemoteClass that are being held on your behalf by the remote JVM and the RemoteDebugger agent.

The following is sample output from the classes command after starting up MTTest:

0x1393768:class(MTTest)
0x1393778:class(sun.tools.debug.Agent)
0x13937a0:class(java.lang.Runtime)
0x1393818:class(java.net.ServerSocket)
0x1393830:class(java.net.PlainSocketImpl)
0x1393840:class(java.net.SocketImpl)
0x1393890:class(java.net.InetAddress)

dump Syntax: dump t@<thread id> | $s<slot id> | 0x<class id> | <name>, where t@<thread id> represents a valid thread ID within the current thread group; $s<slot id> represents the slot/offset to a variable in a stack frame; 0x<class id> represents the numeric identifier for a currently loaded class; or <name> represents the literal this, a valid class name, a field name (for example, class.field), an argument name, or a local variable name.

Uses: RemoteThreadGroup.listThreads, RemoteThread.getStackVariables, RemoteStackVariable.getValue, RemoteDebugger.get, and RemoteDebugger.findClass

This command dumps the detailed description of the specified thread, stack-based variable, class, field, named local variable, or named argument. If an argument, variable or field is requested, its name and value are displayed. If a thread or class is specified, a detailed description of the thread or class is displayed, including instance variables and their current values.

The following is an example of dumping the MTTest class:

T0[1] dump MTTest	// Could also have entered: dump 0x1393768
"MTTest" is not a valid field of (MTTest)0x13a0ca8 
MTTest = 0x1393768:class(MTTest) {
    superclass = 0x1393008:class(java.lang.Thread)
    loader = null

    private static Thread activeThreadQ = null
    private static int threadInitNumber = 2
    public static final int MIN_PRIORITY = 1
    public static final int NORM_PRIORITY = 5
    public static final int MAX_PRIORITY = 10
    static int count = 0
}
T0[1]

Note that the second line is a result of the search algorithm used by the dump command.

list Syntax: list [line number]
Uses: RemoteThread.getCurrentFrame, StackFrame.getRemoteClass, RemoteClass.getSourceFileName, RemoteClass.getGetSourceFile

This command displays one or more source lines for the current thread's current method. There must be a thread in context that is running but in a suspended state. Also, the line number, if specified, must be relative to the top of the source file that defines the current method. Otherwise, if you don't specify a line number, then the current line is displayed. This listing includes the four lines of source immediately before and after the specified line.

The following is how a list with no arguments should look for MTTest at the current breakpoint:

T0[1] list
6               }
7
8              public void run() {
9
10      =>              String  myName = getName();
11
12                      while (true) {
13                              synchronized (sema) {
15                                      if (count < 30) {
T0[1]

The => in line 10 denotes the current line of source.

locals Syntax: locals
Use: RemoteThread.getStackVariables

This command displays all arguments to the current method and local variables that are defined in this stack frame. You must have a thread in context, and you must have compiled your code with the -g option in order to get symbol table information for the local variables and arguments available for debugging.

For example, if you entered locals, you should see the following:

T0[1] locals
Method arguments:
  this = Thread[T0,5,MTThreads]
Local variables:
  myName is not in scope.
T0[1]

The this argument is present in all methods and is pushed on the stack implicitly by the JVM invocation logic. The myName variable is not in scope yet, as you are at a breakpoint at the beginning of the method.

methods
Syntax: methods <classname> | 0x<class id>
Uses: RemoteDebugger.get, or RemoteDebugger.findClass and RemoteClass.getMethods

This command displays all of the methods in the specified class, including the signature of each. The methods list for MTTest should look like this:

T0[1] methods MTTest
void <init>(ThreadGroup, String)
void run()
void main(String[])
T0[1]

The <init> method is a special name and represents the constructor for this class.

print Syntax: print t@<thread id> | $s<slot id> | 0x<class id> | <name>, where t@<thread id> represents a valid thread ID within the current thread group, or $s<slot id> represents the slot/offset to a variable in a stack frame, or 0x<class id> represents the numeric identifier for a currently loaded class, or <name> represents the literal this, a valid class name, a field name (for example, class.field), an argument name, or a local variable name.

Uses: RemoteThreadGroup.listThreads, RemoteThread.getStackVariables, RemoteStackVariable.getValue, RemoteDebugger.get, and RemoteDebugger.findClass

This command displays a simple description of the specified thread, stack-based variable, class, field, named local variable, or named argument. If an argument, variable, or field is requested, then its name and value are displayed. If a thread or class is specified, then the name and ID of the thread or class are displayed.

The following is an example of printing the MTTest class:

T0[1] print MTTest
MTTest = 0x1393768:class(MTTest)
T0[1] 

threadgroups Syntax: threadgroups
Uses: RemoteDebugger.listThreadGroups, RemoteThreadGroup.getName, and RemoteThreadGroup.description

This command displays the name and description of all active thread groups in the remote JVM.

The threadgroups command for MTTest looks like this:

T0[1] threadgroups
1. (java.lang.ThreadGroup)0x13930b8 system
2. (java.lang.ThreadGroup)0x139ec60 main
3. (java.lang.ThreadGroup)0x13a0b00 MTThreads
T0[1]

threads Syntax: threads [thread group name]
Uses: RemoteDebugger.listThreadGroups, RemoteThreadGroup.getName, RemoteThreadGroup.listThreads, RemoteThread.getName, RemoteThread.description, and RemoteThread.getStatus

This command displays the list of threads for the current or specified thread group. If the current or specified thread group has embedded thread groups, their threads are listed as well.

Issuing the threads command for MTTest's named thread group MTThreads should give you something that is similar to the following:

T0[1] threads MTThreads
Group MTThreads:
 1. (MTTest)0x13a0b30 T0 at breakpoint
 2. (MTTest)0x13a0b90 T1 suspended
 3. (MTTest)0x13a0bd0 T2 suspended
T0[1]

where Syntax: where [ all | <thread id> ]
Uses: RemoteThreadGroup.listThreads, RemoteThread.dumpStack, and RemoteStackFrame.toString

This command displays the call stack (the list of methods that were called in order to get to this point) for the current thread (as set with the thread command), all threads (for the current thread group as set with the threadgroup command), or the specified thread (by its ID).

On my system, the command where all gives the following result:

T0[1] where all
Finalizer thread:
Thread is not running (no stack).
Debugger agent:
  [1] sun.tools.debug.Agent.handle (Agent:590)
  [2] sun.tools.debug.Agent.run (Agent:324)
  [3] java.lang.Thread.run (Thread:294)
Breakpoint handler:
  [1] java.lang.Object.wait (Object:152)
  [2] sun.tools.debug.BreakpointQueue.nextEvent (BreakpointQueue:46)
  [3] sun.tools.debug.BreakpointHandler.run (BreakpointHandler:184)
main:
  [1] MTTest.main (MTTest:51)
T0:
[1] MTTest.run (MTTest:12)
T1:
Thread is not running (no stack).
T2:
Thread is not running (no stack).
T0[1]

Breakpoint Commands

These commands allow you to set/remove and control execution flow from a breakpoint. Breakpoints are the fodder for most debugging sessions, in that just about the only way to do anything while debugging is to stop the application. Or, in Java's case, a thread must be stopped unconditionally at some point. That's exactly what a breakpoint does. Once you have established a breakpoint (you have already seen this briefly in step 4 while setting up the debugging session), you execute your program/thread until the execution path reaches that point. When it does, the execution of that thread stops, and you regain control of the remote JVM and your application.

Now you can set up and remove breakpoints, "walk" through your program using the step and next commands, or use cont to continue execution.

stop Syntax 1: stop in <classname>.method | 0x<class id>.method
Uses: RemoteDebugger.findClass or RemoteDebugger.get, RemoteClass.getMethod, and RemoteClass.setBreakpointMethod

Syntax 2: stop at <classname>:line number | 0x<class id>:line number

Uses: RemoteDebugger.findClass or RemoteDebugger.get and RemoteClass.setBreakpointLine

This command sets a breakpoint at the first bytecode instruction of the specified method (Syntax 1) or at the first bytecode instruction of the specified line. If Syntax 2 is used, line number is relative to the beginning of the source file that contains <classname>/<class id>. If stop is issued with no arguments, then the existing breakpoints are displayed. When a breakpoint is placed on a method that is part of a multithreaded application/applet, it applies to all active threads when they cross that method or line of code. The breakpoint remains active until it is removed with the clear command.

The following example lists the current breakpoints, sets one at line 14 of MTTest, and then displays the breakpoint list again:

T0[1] stop
Current breakpoints set:
        MTTest:10
T0[1] stop at MTTest:12
Breakpoint set at MTTest:12
T0[1] stop
Current breakpoints set:
        MTTest:12
        MTTest:10
T0[1]

clear Syntax 1: clear <classname>.method | 0x<class id>.method
Uses: RemoteDebugger.findClass or RemoteDebugger.get, RemoteClass.getMethod, and RemoteClass.clearBreakpointMethod

Syntax 2: clear <classname>:line number | 0x<class id>:line number

Uses: RemoteDebugger.findClass or RemoteDebugger.get, and RemoteClass.clearBreakpointLine

This command clears an existing breakpoint at the first bytecode instruction of the specified method (Syntax 1) or at the first bytecode instruction of the specified line. If Syntax 2 is used, line number is relative to the beginning of the source file that contains <classname>/<class id>. If clear is issued with no arguments, then the existing breakpoints are displayed. When a breakpoint is cleared from a method that is part of a multithreaded application/applet, then it affects all active threads when they cross that method or line of code.

The following example lists the current breakpoints, clears one at the start of MTTest.run, and then displays the breakpoint list again:

T0[1] clear
Current breakpoints set:
        MTTest:12
        MTTest:10
T0[1] clear MTTest.run
Breakpoint cleared at MTTest.run
T0[1] clear
Current breakpoints set:
        MTTest:12
T0[1]

step Syntax: step
Use: RemoteThread.step

This command executes the next instruction of the currently stopped thread. If the next instruction is a method call, execution stops at the first instruction of the method being invoked. An error is generated if there is no current thread or the current thread is not suspended at a breakpoint.

next
(An Undocumented Command)
Syntax: next
Use: RemoteThread.next

Like step, the next command steps execution of the currently stopped thread to the next instruction. But, if the next instruction is a method invocation, then the method is called and control returns to the debugger upon return from the method being executed. At that point, the current instruction is the one immediately following the call. As with step, an error is generated if there is no current thread or the current thread is not suspended at a breakpoint.

cont
Syntax: cont
Uses: RemoteThreadGroup.listThreads, RemoteThread.cont, and RemoteThread.resetCurrentFrameIndex

This command continues the execution of all suspended threads in the default thread group. The command is useful when you have been single-stepping through a thread and want to let the application simply run until the next breakpoint or exception occurs.

Exception Commands

These commands control which exception classes should be caught or ignored by JDB. One of the more interesting aspects of Java is the notion of exceptions. Exceptions are kind of like intelligent breakpoints that you can code logic for directly within your application. They are typically used to trap very specific exceptional situations that should not occur under normal use.

A feature of the JDB API is the ability to register your interest in a specific exception and have it behave like a breakpoint so that you can step through the logic coded in the catch block of the exception handling code. If you choose not to handle a breakpoint in this manner, then the debugging client is notified as if a nonrecoverable breakpoint were reached. That control is returned to the debugger, but you will not be able to step through the exception logic in the catch block.

catch Syntax: catch [ <classname> 0x<class id> ]
Uses: RemoteDebugger.getExceptionCatchList, RemoteDebugger.findClass, or RemoteDebugger.get and RemoteClass.catchException

This command causes the debugger to catch (via DebuggerCallback.exceptionEvent) occurrences of the exception class specified by <classname>/0x<class id> when thrown by the remote JVM. Throwing of the exception class causes execution to stop, as if a breakpoint were placed at the first executable statement of the catch block that is currently active when trying the specified exception. In other words, all try-catch blocks in the application that "catch" the specific exception will become breakpoints. If no class is specified, then the existing caught exceptions are displayed. An error is generated if the specified class is not a subclass of Exception.

ignore Syntax: ignore [ <classname> 0x<class id> ]
Uses: RemoteDebugger.getExceptionCatchList, RemoteDebugger.findClass or RemoteDebugger.get, and RemoteClass.ignoreException

This command causes the debugger to stop catching occurrences of the exception class specified by <classname>/0x<class id> when thrown by the remote JVM. This does not stop the exception from being thrown, but it does stop the debugger from being able to catch the exception as if it were a breakpoint. If no class is specified, then the existing caught exceptions are displayed. An error is generated if the specified class is not a subclass of Exception.

Thread Commands

These commands are used to control the execution state and stack of currently active threads. The thread control commands are something like a manual breakpoint, in that you can stop a thread in its tracks without needing a breakpoint to be coded. This can be extremely useful in situations where you have inadvertently coded an endless loop and can't see where the flawed logic exists. You can also resume execution of a thread you have suspended as well as remove it entirely. Once you have suspended a thread, you can then manipulate the current frame within the call stack that is active. This allows you to examine arguments and local variables in methods that called the currently suspended method.

suspend
Syntax: suspend [ thread-id [ thread-id ... ] ]
Uses: RemoteThreadGroup.listThreads and RemoteThread.suspend

This command suspends (stops) the execution of the specified thread(s) or all nonsystem threads if no thread is specified. This causes a one-time breakpoint to occur at the currently executing instruction in the affected thread(s).

resume
Syntax: resume [ thread-id [ thread-id ... ] ]
Uses: RemoteThreadGroup.listThreads and RemoteThread.resume

This command resumes (continues) execution of the specified thread(s) or all nonsystem threads if no thread is specified. This allows the affected thread to run as if no breakpoints ever existed at the previously suspended code location.

kill (An Undocumented Command) Syntax: kill <thread group name> <thread id>
Uses: RemoteThreadGroup.listThreads, RemoteThread.stop, and RemoteThreadGroup.stop

This command terminates (permanently stops) the execution of either all threads in the specified thread group or just the specified thread (by ID). Once a thread or thread group has been terminated, it may not be resumed, and no breakpoint commands (step, next, cont) may be applied. An error is generated if no arguments are specified or if the specified name or ID is bad.

up
Syntax: up [ n frames ]
Use: RemoteThread.up

This command moves the context of the current stack frame from its current position up one (the default), or n frames. A frame represents the execution state for a method that was being executed prior to calling into another method. The execution state includes the location of the line being executed when the method call was made, the line's arguments (and their current values), and the line's local variables (and their current values). At this point, the method's state is "pushed," and a new frame is created for the method being called. Each time a method is subsequently called from within the next method, the current frame is "pushed" and a new frame is put into context. By moving "up" the call stack, the prior method's arguments and variables may be examined.

down Syntax: down [ n frames ]
Use: RemoteThread.down

This command is used after using the up command. down moves the context of the current stack frame from its current position down one (the default), or n frames. By moving "down" the call stack, you progress toward the current state of execution prior to the last suspended point in the current thread (whether from a breakpoint, caught exception, or explicit call to suspend).

JDB Wrap-Up

As you can see from the number of commands (33 distinct ones), JDB is actually a very powerful debugging facility.

It is an excellent example of how to implement a debugger using Sun's JDB API. Furthermore, it is a great sample application for Java system programming. In reality, it would not take much work to wrap some GUI functionality around the TTY class to make it a little more visually appealing (subclass it, add a multiline text field for the output, set up menu items for the command groups, create pop-up windows, and more). Even if you don't work with the GUI functionality in this manner, you can use JDB to debug your applications on any platform that is supported by the Sun JDK.

What about debugging strategies? The biggest point to keep in mind is that if you are running into problems in a multithreaded application, you are going to want to liberally use breakpoints and exceptions. The nice thing about throwing exceptions in your own code is that you can catch them like breakpoints without having to know specific source line numbers or method names and ignore them as well. (Remember, an exception does not have to mean that a catastrophic error has occurred. It is simply a signal from one piece of code to another.) You can create an exception to throw when a loop counter goes beyond a certain value, when a socket receives a particular message, and so on. The ways that exceptions can be used are truly limitless.

Another important feature to use is the where command. You will not realize you are in a recursive situation unless you see what the call stack looks like. Your logic may have intentional recursion, but it is not a technique that the average business application uses on a regular basis. Once you have a feel for the look of the call stack, you can then use the up and down commands to move your context through the call stack and check out local variables/arguments that might be affecting your execution.

The last strategy is to have an in-depth understanding of how your compiled class files are formatted and how the JVM uses them. That's what is covered in the next two chapters.