jconsole
to monitoring uncaught exceptions, here are a dozen tips that may be worth trying before you launch your favorite IDE’s debugger.Download a PDF of this article
[This article on Java debugging is adapted from Core Java Volume I: Fundamentals, 12th Edition, by Cay S. Horstmann, published by Oracle Press. —Ed.]
Suppose you wrote your Java program and made it bulletproof by catching and properly handling all the exceptions. Then you run it, and it does not work correctly.
Now what? (If you never have this problem, you can skip this article.)
Of course, it is best if you have a convenient and powerful debugger, and debuggers are available as a part of IDEs. That said, here are a dozen tips worth trying before you launch your IDE’s debugger.
Tip 1. You can print or log the value of any variable with code like the following:
System.out.println("x=" + x);
or
Logger.getGlobal().info("x=" + x);
If x
is a number, it is converted to its string equivalent. If x
is an object, Java calls its toString
method. To get the state of the implicit parameter object, print the state of the this
object.
Logger.getGlobal().info("this=" + this);
Most of the classes in the Java library are very conscientious about overriding the toString
method to give you useful information about the class. This is a real boon for debugging. You should make the same effort in your classes.
Tip 2. One seemingly little-known but very useful trick is putting a separate main
method in each class. Inside it, you can put a unit test stub that lets you test the class in isolation.
public class MyClass
{
// the methods and fields
. . .
public static void main(String[] args)
{
// the test code
}
}
Make a few objects, call all methods, and check that each of them does the right thing. You can leave all these main
methods in place and launch the Java Virtual Machine separately on each of the files to run the tests.
When you run an applet, none of these main
methods are ever called.
When you run an application, the JVM calls only the main
method of the startup class.
Tip 3. If you liked the preceding tip, you should check out JUnit. JUnit is a very popular unit testing framework that makes it easy to organize suites of test cases.
Run the tests whenever you make changes to a class, and add another test case whenever you find a bug.
Tip 4. A logging proxy is an object of a subclass that intercepts method calls, logs them, and then calls the superclass. For example, if you have trouble with the nextDouble
method of the Random
class, you can create a proxy object as an instance of an anonymous subclass, as follows:
var generator = new Random()
{
public double nextDouble()
{
double result = super.nextDouble();
Logger.getGlobal().info("nextDouble: "
+ result);
return result;
}
};
Whenever the nextDouble
method is called, a log message is generated.
To find out who called the method, generate a stack trace.
Tip 5. You can get a stack trace from any exception object by using the printStackTrace
method in the Throwable
class. The following code catches any exception, prints the exception object and the stack trace, and rethrows the exception so it can find its intended handler:
try
{
. . .
}
catch (Throwable t)
{
t.printStackTrace();
throw t;
}
You don’t even need to catch an exception to generate a stack trace. Simply insert the following statement anywhere into your code to get a stack trace:
Thread.dumpStack();
Tip 6. Normally, the stack trace is displayed on System.err
. If you want to log or display the stack trace, here is how you can capture it into a string.
var out = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(out));
String description = out.toString();
Tip 7. It is often handy to trap program errors in a file. However, errors are sent to System.err
, not System.out
. Therefore, you cannot simply trap them by running
java MyProgram > errors.txt
Instead, capture the error stream as
java MyProgram 2> errors.txt
To capture both System.err
and System.out
in the same file, use
java MyProgram 1> errors.txt 2>&1
This works in bash
and in the Windows shell.
Tip 8. Having the stack traces of uncaught exceptions show up in System.err
is not ideal. These messages are confusing to end users if they happen to see them, and they are not available for diagnostic purposes when you need them.
A better approach is to log the uncaught exceptions to a file. You can change the handler for uncaught exceptions with the static Thread.setDefaultUncaughtExceptionHandler
method.
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler()
{
public void uncaughtException(Thread t,
Throwable e)
{
// save information in log file
};
});
Tip 9. To watch classes loading, launch the JVM with the -verbose
flag. You will get a printout such as in Figure 1.
Figure 1. What you see after using the -verbose
flag.
This report can occasionally be helpful to diagnose classpath problems.
Tip 10. The -Xlint
option tells the compiler to spot common code problems. For example, if you compile with the command
javac -Xlint sourceFiles
the compiler will report missing break
statements in switch
statements. (The word lint originally described a tool for locating potential problems in C programs but is now generically applied to any tools that flag constructs that are questionable but not illegal.)
You will get messages such as “warning: [fallthrough] possible fall-through into case.”
The string in square brackets identifies the warning category. You can enable and disable each category. Since most of them are quite useful, it seems best to leave them all in place and disable only those that you don’t care about, as follows:
javac -Xlint:all,-fallthrough,-serial sourceFiles
You can see a list of all available warnings by using this command.
javac --help -X
Tip 11. The JVM supports the monitoring and management of Java applications by allowing the installation of agents in the virtual machine that track memory consumption, thread usage, class loading, and so on. These features are particularly important for large and long-running Java programs, such as application servers.
As a demonstration of these capabilities, the JDK ships with a graphical tool called jconsole
that displays statistics about the performance of a virtual machine (see Figure 2). Start your program, and then start jconsole
and pick your program from the list of running Java programs. See the documentation for more information.
Figure 2. jconsole gives you a wealth of information about your running program.
Tip 12. Java Mission Control is a professional-level profiling and diagnostics tool, available for download.
Like jconsole
, Java Mission Control can attach to a running virtual machine. It can also analyze the output from Oracle Java Flight Recorder, a tool that collects diagnostic and profiling data from a running Java application.
Cay S. Horstmann (@cayhorstmann) has written many books on C++, Java, and object-oriented development, and is a frequent speaker at computer industry conferences. For four years, Horstmann was VP and CTO of an internet startup that went from three people in a tiny office to a public company. He is now a computer science professor at San Jose State University.