The first step in trying to solve any problem is to gather as much
evidence and information as possible. If you can picture a crime scene,
you know that everything is checked, cataloged and analyzed before any
conclusions are reached. When debugging a program, you do not have weapons,
hair samples, or fingerprints, but there is plenty of evidence you can
gather that might contain or ultimately lead to the solution. This section
explains how to gather that evidence.
Installation and Environment
The JavaTM platform is a fast-moving and
changing technology. You might have more than one release installed
on your system, and those releases might have been installed as part
of another products installation. In an environment with mixed releases,
a program
can experience problems due to changes to the platform in a new version
or release.
For example, if classes, libraries, or Windows registry entries from previous
installations remain on your system after an upgrade, there is
a chance the new software mix is causing your problems and needs to be
investigated and ruled out. Opportunities for problems related to mixed
software releases have increased with the use of
different release tools to deliver the Java platform software.
The section on
Version Issues at the end of this chapter
provides a complete list of major
Java platform release and version information to help you rule out software
release issues. This next section highlights the most common problems you are
likely to encounter.
Class Path
In the Java 2 platform, the CLASSPATH environment variable is
needed to specify the application's own classes only,
and not the Java platform classes as was required in earlier
releases. So it is possible your CLASSPATH environment
variable is pointing at Java platform classes from earlier
releases and causing problems.
To examine the CLASSPATH , type the following at the
command line:
Windows 95/98/NT:
echo %CLASSPATH%
Unix Systems:
echo $CLASSPATH
Java classes are loaded on a first come, first served basis from the
CLASSPATH list.
If the CLASSPATH variable contains a reference to a
lib/classes.zip file, which in turn points to a different
Java platform installation, this can cause incompatible classes to be
loaded.
Note:
In the Java 2 platform, the system classes are chosen
before any class on the CLASSPATH list to minimize the
possibility of any old broken Java classes being loaded instead of a
Java 2 class of the same name.
The CLASSPATH variable can get its settings from
the command line or from configuration settings such as those
specified in the User Environment on Windows NT, an
autoexec.bat file, or a shell startup file like
.cshrc on Unix.
You can control the classes the Java1 Virtual Machine (VM) uses
by compiling your program with a special command-line option
that lets you supply the CLASSPATH you want.
The Java 2 platform option and parameter is
-Xbootclasspath classpath , and earlier releases
use -classpath classpath and
-sysclasspath classpath .
Regardless of which release you are running,
the classpath parameter specifies the system and user
classpath, and zip or Java ARchive (JAR) files to be used
in the compilation.
To compile and run the Myapp.java program with a system
CLASSPATH supplied on the command line,
use the following instructions:
Windows 95/98/NT:
In this example, the Java platform is installed in the
C:\java directory. Type everything on one line:
javac -J-Xbootclasspath:c\java\lib\tools.jar;c:
\java\jre\lib\rt.jar;c:\java\jre\lib\i18n.jar;.
Myapp.java
You do not need the -J runtime flag to run the
compiled Myapp program, just type the following
on one line:
java -Xbootclasspath:c:\java\jre\lib\rt.jar;c:
\java\jre\lib\i18n.jar;. Myapp
Unix Systems:
In this example, the Java platform is installed in the
/usr/local/java directory. Type everything on one line:
javac -J-Xbootclasspath:/usr/local/java/lib/tools.jar:
/usr/local/java/jre/lib/rt.jar:
/usr/local/java/jre/lib/i18n.jar:. Myapp.java
You do not need the -J runtime flag to run the
compiled Myapp program, just type the following
on one line:
java -Xbootclasspath:/usr/local/java/jre/lib/rt.jar:
/usr/local/java/jre/lib/i18n.jar:. Myapp
Class Loading
Another way to analyze CLASSPATH problems
is to locate where your application is loading its classes.
The -verbose option to the java command shows
which .zip or .jar file a class comes from when
it is loaded. This way, you will be able to tell if it came from the Java
platform zip file or from some other application's JAR file.
For example, an application might be using the
Password class you wrote for it or it might
be loading a Password class
from an installed integrated development environment (IDE) tool.
You should see each jar and zip file named as in the example below:
$ java -verbose SalesReport
[Opened /usr/local/java/jdk1.2/solaris/jre/lib/rt.jar
in 498 ms]
[Opened /usr/local/java/jdk1.2/solaris/jre/lib/i18n.jar
in 60 ms]
[Loaded java.lang.NoClassDefFoundError from
/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]
[Loaded java.lang.Class from
/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]
[Loaded java.lang.Object from
/usr/local/java/jdk1.2/solaris/jre/lib/rt.jar]
Including Debug Code
A common way to add diagnostic code to an application is to use
System.out.println statements at strategic locations
in the application. This technique is fine during development, providing
you remember to remove them all when you release your product. However,
there are other approaches that are just as simple, do not affect the
performance of your application, and do not display messages that you do
not want your customers to see. The following are two techniques that
overcome the problems with simple System.out.println statements.
Turning Debug Information On at Runtime
The first alternative to the classic println debug statements
is to turn on debugging information at runtime. One advantage to this is
you do not need to recompile any code if problems appear at the testing
stage or on a customer site.
Another advantage is that sometimes software
problems can be attributed to race conditions where the same segment
of code behaves unpredictably due to timing between other program
interactions. If you control your debug code from the command line instead
of adding println debug statements, you can
rule out sequence problems caused by race conditions coming
from the println code. This technique also saves you
adding and removing println debug statements and
having to recompile your code.
This technique requires you to use a system property as a debug flag
and include application code to test that system property value.
To turn on debug information from the command line at run time,
start the application and set
the debug system property to true as follows:
java -Ddebug=true TestRuntime
The source code for the TestRuntime class needs to examine
this property and set the debug boolean flag as follows:
public class TestRuntime {
boolean debugmode; //global flag that we test
public TestRuntime () {
String dprop=System.getProperty("debug");
if ((dprop !=null) && (dprop.equals("yes"))){
debugmode=true;
}
if (debugmode) {
System.err.println("debug mode!");
}
}
}
Creating Debug and Production Releases at Compile Time
As mentioned earlier, one problem with adding
System.out.println debug statements to your code is
finding and removing them before you release the product.
Apart from adding unnecessary code, println debug
statements can contain information you do not want your customers to
see.
One way to remove System.out.println debug statements
from your code is to use the following compiler optimization to
remove pre-determined branches from your code at compile time and achive
something similar to a debug pre-processor.
This example uses a static dmode
boolean flag that when set to false
results in the debug code and the debug test statement being removed.
When the dmode value is set to true ,
the code is included in the compiled class file and is available
to the application for debugging purposes.
class Debug {
//set dmode to false to compile out debug code
public static final boolean dmode=true;
}
public class TestCompiletime {
if (Debug.dmode) { // These
System.err.println("Debug message"); // are
} // removed
}
Using Diagnostic Methods
You can use diagnostic methods to request debug information from the
Java VM. The following two
methods from the Runtime class trace the method calls and Java VM
byte codes your application uses. As both these methods produce a lot of output,
it is best to trace very small amounts of code, even as little as one
line at a time.
To enable trace calls so you will see the output, you have
to start the Java VM with the
java_g or java -Xdebug interpreter commands.
To list every method as it is invoked at runtime,
add the following line before the code you wish to start tracing and add
a matching traceMethodCalls line with the argument set to false to turn the tracing off. The tracing information is displayed on the standard output.
// set boolean argument to false to disable
Runtime.getRuntime().traceMethodCalls(true);
callMyCode();
Runtime.getRuntime().traceMethodCalls(false);
To see each line as bytecodes as they are executed, add
the following line to your application code:
// set boolean argument to false to disable
Runtime.getRuntime().traceInstructions(true);
callMyCode();
Runtime.getRuntime().traceInstructions(false);
You can also add the following line to your application to
dump your own stack trace using the dumpStack
method from the Thread class. The output from a stack trace
is explained in
Analyzing Stack Traces, but for now you can think
of a stack trace as a snapshot of the
current threads running in the Java VM.
Thread.currentThread().dumpStack();
Adding Debug Information
Local variable information is not included in the core
Java platform system classes. So, if you use a debug tool
to list local variables for system classes where you place
stop commands, you will get the following
output, even when you compile with the -g flag
as suggested by the output. This output is from a jdb
session:
main[1] locals
No local variables: try compiling with -g
To get access to the local variable information, you have to obtain the
source (src.zip or src.jar ) and recompile it
with a debug flag.
You can get the source for most java.* classes with the binary
downloads from java.sun.com.
Once you download the src.zip or src.jar file, extract only the files you need.
For example, to extract the String class, type the
following at the command line:
unzip /tmp/src.zip src/java/lang/String.java
or
jar -xf /tmp/src.jar src/java/lang/String.java
Recompile the extracted class or classes with the -g option.
You could also add your own additional diagnostics to the source file at this
point.
javac -g src/java/lang/String.java
The Java 2 javac compiler gives you more options than just the
original -g option for debug code, and you can reduce the size
of your classes by using -g:none , which gives you
on average about a 10 percent reduction in size.
To run the application with the newly compiled debug class or classes,
you need to
use the bootclasspath option so these new classes
are picked up first.
Type the following on one line with a space before myapp .
Win95/NT Java 2 Platform:
This example assumes the Java platform is installed in c:\java ,
and the source files are in c:\java\src :
jdb -Xbootclasspath:c:\java\src;c:\java\jre\lib\rt.jar;c:
\java\jre\i18n.jar;. myapp
Unix Systems:
This example assumes the Java platform is installed in
c:\java , and the source files are in
c:\java\src .
jdb -Xbootclasspath:/usr/java/src;
/usr/java/jre/lib/rt.jar;
/usr/java/jre/i18n.jar;. myapp
The next time you run the locals command you will see
the internal fields of the class you wish to analyze.
[TOP]
_______
1 As used on this web site,
the terms "Java virtual
machine" or "JVM" mean a virtual machine
for the Java platform.
|