Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

The State of Debugging in Java

DZone's Guide to

The State of Debugging in Java

Debugging has come a long way from the jdb days. Here, we cover the variety of Java debuggers out there and some common practices devs use.

· Java Zone ·
Free Resource

Get the Edge with a Professional Java IDE. 30-day free trial.

This article is featured in the new DZone Guide to Java: Development and Evolution. Get your free copy for more insightful articles, industry statistics, and more!

It is a fair statement, I think, to suggest that Java developers spend as much — if not more — time working through the bugs in their code in one form or another than they do actually writing the code. Ideally, those bugs will be sorted out because the unit test suite caught them before they reached production, but regardless of who, what, or where, bugs are a fact of any software developer’s life.

The Java platform has seen some serious changes since its introduction to the world via the HotJava browser at COMDEX in 1995. In the first release of the JDK, the only debugging tool available was the text-based debugger jdb — assuming you don’t count System.out.println, of course. Since that time, however, and after a few fits and starts, the Java platform has “bulked out” its debugging capabilities in some serious ways. In some cases, those enhancements came rather quietly, hidden behind some of the more glitzy features of the platform’s evolution.

Particularly if you’re a long-term Java developer like me, it can be helpful to take a quick overview of what’s available before diving back into the debugging process. In particular, my goal is to avoid some of the more developer practices — such as logging or writing unit tests — to focus more on what tools, technologies, and ideas for debugging Java systems. Bear in mind, some of these tools are more often categorized as “management” tools, but ultimately, management and debugging both require the same capabilities — visibility into the underlying virtual machine. It’s only the purposes to which they are put to use that really serve to distinguish between “debugger” and “monitoring” tools.

With that, we begin.

Java Management Extensions (JMX)

The JMX API was one of the most fundamental introductions into the Java platform from a debugging perspective, yet for much of its early days, it was hailed purely as a management and monitoring tool. In fact, one of the key changes that came along with the JMX API was the introduction of some core JMX MBeans (managed beans) from within the JVM itself — in other words, the custodians of the JVM at the time chose to expose parts of the JVM’s internal workings as monitorable assets. This, in turn, means that we can use said assets as part of a debugging strategy. 

Because each JVM implementation is free to expose its own additional MBeans, additional ones may be present beyond the ones discussed here, but at a minimum, each compliant JVM implementation is expected to expose beans around ClassLoaders, memory management facilities (usually across several beans: GarbageCollector, Memory, MemoryManager, and MemoryPool), and — most importantly, from a debugging perspective — Threading. In particular, the Threading MBean has several methods, such as findMonitorDeadlockedThreads(), getThreadInfo(), and dumpAllThreads(), that can be incredibly helpful in tracking down deadlock situations. Similarly, the application server you use, such as Tomcat, will frequently offer a number of MBeans that can be used to either monitor or debug what’s happening inside of a Java application—fire up your application under your favorite JMX-aware tool, such as jvisualvm or jconsole, to get a sense of all of the MBeans that are exposed.

JDK Command-Line Tools

The JDK itself ships with several tools, many (if not most) of which are labeled as “experimental” or “unsupported,” largely because they have historically been intended more as an example of what one could do with the support the JVM provides, rather than trying to be a fully bulletproofed tool. That said, the list of tools provided is quite surprising, and many are useful in their own right. As of Java 8, that list includes: 

  • jps: A simple command that lists all of the Java processes currently running on the machine. It
    returns the “JVMID,” an identifier that is often used with other tools to uniquely identify this executing JVM. Most often, the JVMID is the exact same as the operating system’s process identifier, or PID, but it can include hostname and port, so that a tool can connect remotely to a running process, assuming the network facilities permit such communication.

  • jinfo: An “information dump” utility. When given a JMVID, it will connect to the target JVM and “dump” a large amount of information about the process’s environment, including all of its system properties, the command-line used to launch the JVM, and the nonstandard
    JVM options used (meaning the “-XX” flags).

  • jcmd: A “command” utility that can issue a number of different debugging/monitoring commands to the target JVM. Use “jcmd <pid> help” to see a list of commands that can be sent—one of the most useful will be GC.heap_dump to take a snapshot of the entire
    JVM heap, for offline analysis.

  • jmap: Another “heap dump” utility that can not only dump the JVM heap into the same format that jcmd uses, but can also track and dump ClassLoader statistics, which can be helpful to discover ClassLoader class leaks.

  • jhat: The “Java heap analyzer tool.” It takes a heap dump generated by any of the other utilities and provides a tiny HTTP-navigable server to examine the contents. While the user interface isn’t amazing and clearly hasn’t been updated in years, jhat has one unique facility to it that is quite useful: the ability to write OQL queries to examine the contents of the heap without having to manually click through every object.

  • jstack: Does a Java thread stack dump of any running JVM process. A critical first step to diagnosing any thread-deadlock or high-contention errors in a running JVM, even if that JVM is in production.

  • jstat: A “Java statistics” utility. Run “jstat -options” to see the full list of commands that can be passed to the target JVM, most of which are GC-related.

The source code for these tools is even more important than the tools themselves, because that leads us directly into the last tidbit we have space and time to cover.

JDB Scripting

The ubiquitous and “ancient” debugger, jdb, has a few surprises in store for those who spend a little time getting to know it. Although the user interface, being text-based, leaves a wee bit to be desired, the fact that it is a text-based interface means that it can be scripted by a text file.

Consider a simple Java application that does some really trivial field manipulation before terminating:

public class App {

    private static int count = 0;
    private static String message = “”;

    public static void main(String...args) {
        while (true) {
            countandand;
            if (count < 10) {
                message = “I’ m less than 10”;
            } else if (count > 20) {
                message = “I’ m about to quit”;
            }
            if (count > 30)
                System.exit(0);
        }
    }
}


Assume for the moment that the bug is that the app is supposed to terminate once the count field has reached 21, not 31. Normally, from a Java IDE, we could set the breakpoint on the line containing the System.exit() method call, but if this is only happening in production, it can be quite the fight to get the system administrators to allow us to install a Java IDE on the
production machines (assuming it’s not in a cloud datacenter somewhere); however, if the JDK is installed, then jdb is there.

Custom Tools

A recent survey by a Java tools vendor discovered that almost a quarter of the Java developers surveyed had built their own profiling tools. Although nobody would ever suggest that one of these home-grown tools is generally as useful as a commercial profiler, building a small suite of specific-purpose one-off tools can be immensely helpful. Thanks to the architecture of the JDK, it’s straightforward to build debugging tools, as well. 

The reason this is possible is simple: from roughly JDK 1.2 through to Java5, the JVM has steadily grown more and more “programmable” from a visibility perspective, culminating in a critical feature in the Java 5 release: the Java Platform Debug Architecture (JPDA). Put succinctly, JPDA is a platform for building tools and agents that can be connected to — or sometimes even hosted within — the JVM to gain that critical view. What’s more important to the Java developer is that the cost of using the instrumentation within the JVM is effectively nil — that is, the instrumentation is “always on,” regardless of whether a debugger is connected to it or not.

Most of the time, such a tool will be written against the Java Debugger Interface (JDI), which lives in the com. sun.jdi package and is documented at <<JDI-URL>>. Thanks to the inclusion of the Nashorn engine as a part of the JDK, coupled with the fact that Nashorn (like its predecessor, Rhino) has full interoperability with any Java API, it’s trivial to write debugger utilities using JavaScript. Full discussion of the JDI is well beyond the scope of this article, but most Java developers will find it well worth the investment in time.

Summary

The Java world is filled with a number of debugging tools well beyond what comes straight out of the box with the JDK. Chief among these will be the Java IDE itself, and developers should spend time getting to know what the IDE offers. Many, for example, offer the ability to conditionally stop on a breakpoint based on a runtime-evaluated expression; this would make our earlier debugging example straightforward to diagnose from the IDE, by setting a conditional breakpoint on the System.exit method to examine the value of App.count and either break (if it is a value other than the expected 21) or continue execution without pausing.

Never look to invent new tools that already exist. Certainly, the Java ecosystem is filled with tools that provide powerful functionality — for example, in addition to being a powerful programming language in its own right, AspectJ’s ability to “weave” against compiled bytecode and inject arbitrary Java code provides an opportunity to “slip in” highly focused debugging utilities when necessary. For example, using the above App example again, a developer familiar with AspectJ could write a simple aspect that defines a joinPoint on the call to System.exit within the App
class, and print the value of App.count before allowing the execution to continue. This is just one of many different ways to use AspectJ, and that, in turn, is just one of a number of different tools.

Most of all, the key here is to “practice, practice, practice.” Trying to learn how to use these tools while in the heated moment of trying to fix a production-stopping bug is not going to yield the best results; in fact, it’s more likely to yield confusion and/or misdirection, which is the last thing anybody wants during a crisis. Create some code with bugs in it, ignore the IDE, and investigate these tools as a way to “test-drive” them. Get to know them before you need them, and they’ll feel like trusted friends when the
time comes.

This article is featured in the new DZone Guide to Java: Development and Evolution. Get your free copy for more insightful articles, industry statistics, and more!

Get the Java IDE that understands code & makes developing enjoyable. Level up your code with IntelliJ IDEA. Download the free trial.

Topics:
java ,debugging ,jmx ,command-line tools ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}