Monday, January 17, 2022

12 handy debugging tips from Cay Horstmann’s Core Java

From using 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.

Oracle Java Exam Prep, Oracle Java Preparation, Core Java, Java Career, Java Skills, Oracle Java Guides
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.

Source: oracle.com

Related Posts

0 comments:

Post a Comment