Debugging Java app without IDE

Command line debugger part of OpenJDK: jdb

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

Some bugs are hard to replicate on personal computer but easily replicated on production or test machine, it is a common situation professional Java developers deal with. To debug such problems OpenJDK provides 2 tools, remote debugging and jdb. This post we will focus on jdb.

For Java applications typical production and test machines are linux servers without display manager, only command line tools are available. Here we can not use professional IDE like Intellij Idea, Eclipse or Netbeans. In such scenario we can use jdb. jdb is a command line debugger, it is part of OpenJDK.


Troubleshoot java application with the jdb Utility

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.

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). -g option generates all debugging information, including local variables. By default, only line number and source file information is generated.

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, we have only passed class name Test. 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

For this post we will not see remote debugging in detail.


debugging and monitoring

Following is the command to attach jdb with Java program Test:

/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
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("Third Line of main function");

execute current line using step:

main[1] step
> Third Line of main function

Step completed: "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

All available commands in jdb using help:

main[1] help
** command list **
connectors                -- list available connectors and transports in this VM

run [class [args]]        -- start execution of application's main class

threads [threadgroup]     -- list threads
thread <thread id>        -- set default thread
suspend [thread id(s)]    -- suspend threads (default: all)
resume [thread id(s)]     -- resume threads (default: all)
where [<thread id> | all] -- dump a thread's stack
wherei [<thread id> | all]-- dump a thread's stack, with pc info
up [n frames]             -- move up a thread's stack
down [n frames]           -- move down a thread's stack
kill <thread id> <expr>   -- kill a thread with the given exception object
interrupt <thread id>     -- interrupt a thread

print <expr>              -- print value of expression
dump <expr>               -- print all object information
eval <expr>               -- evaluate expression (same as print)
set <lvalue> = <expr>     -- assign new value to field/variable/array element
locals                    -- print all local variables in current stack frame

classes                   -- list currently known classes
class <class id>          -- show details of named class
methods <class id>        -- list a class's methods
fields <class id>         -- list a class's fields

threadgroups              -- list threadgroups
threadgroup <name>        -- set current threadgroup

stop [go|thread] [<thread_id>] <at|in> <location>
-- set a breakpoint
-- if no options are given, the current list of breakpoints is printed
-- if "go" is specified, immediately resume after stopping
-- if "thread" is specified, only suspend the thread we stop in
-- if neither "go" nor "thread" are specified, suspend all threads
-- if an integer <thread_id> is specified, only stop in the specified thread
-- "at" and "in" have the same meaning
-- <location> can either be a line number or a method:
--   <class_id>:<line_number>
--   <class_id>.<method>[(argument_type,...)]
clear <class id>.<method>[(argument_type,...)]
-- clear a breakpoint in a method
clear <class id>:<line>   -- clear a breakpoint at a line
clear                     -- list breakpoints
catch [uncaught|caught|all] <class id>|<class pattern>
-- break when specified exception occurs
ignore [uncaught|caught|all] <class id>|<class pattern>
-- cancel 'catch' for the specified exception
watch [access|all] <class id>.<field name>
-- watch access/modifications to a field
unwatch [access|all] <class id>.<field name>
-- discontinue watching access/modifications to a field
trace [go] methods [thread]
-- trace method entries and exits.
-- All threads are suspended unless 'go' is specified
trace [go] method exit | exits [thread]
-- trace the current method's exit, or all methods' exits
-- All threads are suspended unless 'go' is specified
untrace [methods]         -- stop tracing method entrys and/or exits
step                      -- execute current line
step up                   -- execute until the current method returns to its caller
stepi                     -- execute current instruction
next                      -- step one line (step OVER calls)
cont                      -- continue execution from breakpoint

list [line number|method] -- print source code
use (or sourcepath) [source file path]
-- display or change the source path
exclude [<class pattern>, ... | "none"]
-- do not report step or method events for specified classes
classpath                 -- print classpath info from target VM

monitor <command>         -- execute command each time the program stops
monitor                   -- list monitors
unmonitor <monitor#>      -- delete a monitor
read <filename>           -- read and execute a command file

lock <expr>               -- print lock info for an object
threadlocks [thread id]   -- print lock info for a thread

pop                       -- pop the stack through and including the current frame
reenter                   -- same as pop, but current frame is reentered
redefine <class id> <class file name>
-- redefine the code for a class

disablegc <expr>          -- prevent garbage collection of an object
enablegc <expr>           -- permit garbage collection of an object

!!                        -- repeat last command
<n> <command>             -- repeat command n times
# <command>               -- discard (no-op)
help (or ?)               -- list commands
dbgtrace [flag]           -- same as dbgtrace command line option
version                   -- print version information
exit (or quit)            -- exit debugger

<class id>: a full class name with package qualifiers
<class pattern>: a class name with a leading or trailing wildcard ('*')
<thread id>: thread number as reported in the 'threads' command
<expr>: a Java(TM) Programming Language expression.
Most common syntax is supported.

Startup commands can be placed in either "jdb.ini" or ".jdbrc"
in user.home or user.dir

coming out of jdb:

quit


Conclusion

OpenJDK provides many amazing troubleshooting and diagnosis tools. These tools help you to fix issues in your production application quickly. jdb can be a great help when there is no way other than debugging the application, and your favourite IDE is not available.

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!

Resources:

  1. javac
  2. Java Platform Debugger Architecture