Debugging Java app when you don't have access to IDE

JDK command line debugger jdb

Posted by Vipin Sharma on Monday, February 1, 2021 Tags: OpenJDK tools   6 minute read

This is draft post, work in progress:

###

For Java applications typical production/test machines are linux servers without display managers, only command line tools are available. Some bugs are hard to replicate on desktop but easily replicated on production/test machine, it is a common situation for professional Java developers. To debug such problems JDK provides 2 tools, remote debugging and jdb. Remote debugging is useful, but debugging this very slow experience. jdb is command line debugger as part of JDK, we can use this for inspection or debugging Java application.

jdb is available in jdk/bin directory. It uses the Java Debug Interface (JDI) to launch or connect to the target JVM. The Java Debug Interface (JDI) provides a Java programming language interface for debugging Java programming language applications. JDI is a part of the Java Platform Debugger Architecture.


Troubleshoot java application with the jdb Utility

This section we will see how to attach jdb to java application and start debugging and monitoring.

jdb command

This is format of the jdb command:

jdb [options] [classname] [arguments]

options:    This represents the jdb command-line options (e.g. attach, launch).
classname:  This represents the name of the main class to debug.
arguments:  This represents the arguments that are passed to the main() method of the class.


Java program we will be debugging in this post

Following is sample class we are going to debug and try to understand different features available. It is important to compile this class with -g option (javac -g Test.java) in order to see local variables while debugging.

public class Test
{
	public static void main(String[] args)
	{
		System.out.println("First Line of main function");
        System.out.println("Second Line of main function");
        System.out.println("Third Line of main function");
		
		int i=0;
		System.out.println("i: " + i);
		i = 2;
		System.out.println("i: " + i);

		while(true)
		{
		}
	}
}


Attach jdb to Java application

Below command is the most common way to start application with jdb debugger, here we are not passing any jdb option, only class name Test is passed, and class Test doesn’t require any argument.

jdb Test

In this way we start executing main class Test in the similar way we start in professional IDE eclipse or intellij. jdb stops the JVM before executing that class’s first instruction.

Another way to use the jdb command is by attaching it to a JVM that’s already running. Syntax for starting JVM with debugger port is:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 Test

To attach jdb with this remote jvm use below syntax:

jdb -attach 5005


debugging and monitoring

For this post we are not using remote debugging, using following command to attach with jvm:

/jdk/bin/jdb Test
Initializing jdb ...

Setting a break point at line number 5 using stop:

> stop at Test:5
Deferring breakpoint Test:5.
It will be set after the class is loaded.

Start execution of application’s main class using run:

> run Test
run  Test
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint Test:5

Breakpoint hit: "thread=main", Test.main(), line=5 bci=0
5    		System.out.println("First Line of main function");

execute current line using step:

main[1] step

> First Line of main function

Step completed: "thread=main", Test.main(), line=6 bci=8
6            System.out.println("Second Line of main function");

execute current line using step:

main[1] step

> Second Line of main function

Step completed: "thread=main", Test.main(), line=7 bci=16
7            System.out.println("Thrid Line of main function");

execute current line using step:, this is also an example of messed up console print by 2 threads.

main[1] step
> Third Line of main fu
nStep completed: ction
"thread=main", Test.main(), line=9 bci=24
9    		int i=0;

execute current line using step:

main[1] step
>
Step completed: "thread=main", Test.main(), line=10 bci=26
10    		System.out.println("i: " + i);

printing local variable i using print:

main[1] print i
i = 0

printing all local variables in current stack frame using locals:

main[1] locals
Method arguments:
args = instance of java.lang.String[0] (id=841)
Local variables:
i = 0

dump a thread’s stack using where:

main[1] where
[1] Test.main (Test.java:10)

list threads in running application using threads:

main[1] threads
Group system:
(java.lang.ref.Reference$ReferenceHandler)804 Reference Handler   running
(java.lang.ref.Finalizer$FinalizerThread)805  Finalizer           cond. waiting
(java.lang.Thread)806                         Signal Dispatcher   running
(java.lang.Thread)803                         Notification Thread running
Group main:
(java.lang.Thread)1                           main                running
Group InnocuousThreadGroup:
(jdk.internal.misc.InnocuousThread)807        Common-Cleaner      cond. waiting

continue execution from the breakpoint using cont:

main[1] cont
> i: 0
i: 2

coming out of jdb:

quit

Conclusion

Knowing features like this helps you get the best java jobs, that’s why to help you I wrote ebook 5 steps to Best Java Jobs. Download this step by step guide for free!