- Structure Overview
- What is the Java Platform Debugger Architecture (JPDA)?
- Reference implementation
- Using JPDA
- Components
- debuggee
- Java Virtual Machine (VM)
- back-end
- communications channel
- front-end
- User Interface (UI)
- Debugger Interfaces
- Java Virtual Machine Tool Interface (JVM TI)
- Java Debug Wire Protocol (JDWP)
- Java Debug Interface (JDI)
- Tutorial: Debug your first Java application
- What Is Debugging?
- Examine the code
- Set breakpoints
- Run the program in debug mode
- Analyze the program state
- Step through the program
- Stop the debugger session and rerun the program
Structure Overview
The Java Platform Debugger Architecture is structured as follows:
Components Debugger Interfaces / |--------------| / | VM | debuggee ----( |--------------| JVM TI - Java VM Tool Interface \ | back-end | \ |--------------| / | comm channel -( | JDWP - Java Debug Wire Protocol \ | |--------------| | front-end | |--------------| JDI - Java Debug Interface | UI | |--------------|
What is the Java Platform Debugger Architecture (JPDA)?
JPDA is a multi-tiered debugging architecture that allows tools developers to easily create debugger applications which run portably across platforms, virtual machine (VM) implementations and JDK versions.
JPDA consists of three layers:
JVM TI — Java VM Tool Interface Defines the debugging services a VM provides. JDWP — Java Debug Wire Protocol Defines the communication between debuggee and debugger processes. JDI — Java Debug Interface Defines a high-level Java language interface which tool developers can easily use to write remote debugger applications.
Reference implementation
In addition to the specification of these interfaces, Oracle also provides a reference implementation, which consists of:
- JVM TI implementations on multiple VMs (see VM documentation).
- A back-end which uses JVM TI to implement the debuggee side of JDWP.
- A front-end which uses the debugger side of JDWP to implement JDI.
- Two simple example debugger applications which are built on JDI.
This provides a layered implementation in which any layer may be substituted.
Using JPDA
A debugger developer may hook into JPDA at any layer. Since the JDI is the highest level and easiest to use we encourage developers to use this interface. Suppose a company develops a debugger using JDI. They can use it with the reference implementation and it will automatically work with the VMs and platforms that Oracle supports. It can also work, for example, with the reference implementation front-end and a debuggee running another company’s VM that implements JDWP (which might use or by-pass JVM TI).
Some debuggers are built on top of lower layers, JDWP (for example if the front-end is not written in the Java language) or JVM TI (for specialized debuggers which need low-level functionality).
Components
debuggee
The debuggee is the process being debugged, it consists of the application being debugged (not shown), the VM running the application and the back-end of the debugger.
Java Virtual Machine (VM)
This refers to the VM running the application being debugged.
The debugger architecture is being designed for use in wide spectrum of VM implementations.
The VM implements the Java Virtual Machine Tool Interface (JVM TI).
back-end
The back-end of the debugger is responsible for communicating requests from the debugger front-end to the debuggee VM and for communicating the response to these requests (including desired events) to the front-end. The back-end communicates with the front-end over a communications channel using the Java Debug Wire Protocol (JDWP). The back-end communicates with the debuggee VM using the Java Virtual Machine Tool Interface (JVM TI).
It is clear from experience that debugger support code, running on the debuggee and written in Java, contends with the debuggee in ways that cause hangs and other undesired behavior. Thus, the back-end is native code. This, in turn, implies that the JVM TI be a pure native interface.
communications channel
The communications channel is the link between the front and back ends of the debugger. It can be thought of as consisting of two mechanisms:
- A connector. A connector is a JDI object that is the means by which a connection is established between the front and back-ends. JPDA defines three types of connectors:
- listening connectors: The front-end listens for an incoming connection from the back-end
- attaching connectors: The front-end attaches to an already running back-end.
- launching connectors: The front-end actually launches the java process that will run the debuggee code and the back-end.
- A transport. A transport is the underlying mechanism used to move bits between the front-end and the back-end.
JPDA includes service provider interfaces to allow the development and deployment of connector and transport implementations. These service provider interfaces allow debugger and other tool vendors to develop new connector implementations and provide addition transport mechanisms over and beyond the socket and shared memory transport provided by Oracle. The service provider interfaces in JDI are specified in the com.sun.jdi.connect.spi package.
In addition to the service provider interfaces in JDI, JPDA also defines a transport library interface called the Java Debug Wire Protocol Transport Interface. A transport library is loaded by the JDWP agent in the target VM and is used to establish a connection to the debugger and to transport JDWP packets between the debugger and the VM.
front-end
The debugger front-end implements the high-level Java Debug Interface (JDI). The front-end uses the information from the low-level Java Debug Wire Protocol (JDWP).
User Interface (UI)
The user interface to the debugger is not specified; the intent is that tool vendors will provide value added implementations. We provide an example simple graphical user interface (GUI) which serves as test harness and as a starting point for the development of more complex GUIs. A version of JDB is also available as an example.
The example UIs are clients of the Java Debug Interface (JDI).
Debugger Interfaces
Java Virtual Machine Tool Interface (JVM TI)
A native interface implemented by the VM.
Defines the services a VM must provide for debugging. Includes requests for information (for example, current stack frame), actions (for example, set a breakpoint), and notification (for example, when a breakpoint has been hit). A debugger may make use of VM information other than this (for example, Java Native Interface (JNI)), but this is the source of all debugger specific information.
Specifying the VM Interface allows any VM implementor to plug easily into the debugging architecture. It also allows alternate communication channel implementations. VM implementations which do not adhere to this interface can still provide access via the Java Debug Wire Protocol (JDWP).
Java Debug Wire Protocol (JDWP)
Defines the format of information and requests transferred between the debuggee process and the debugger front-end. It does not define the transport mechanism (socket, serial line, shared memory, …).
The specification of the protocol allows the debuggee and debugger front-end to run under separate VM implementations and/or on separate platforms. It also allows the front-end to be written in a language other than Java, or the debuggee to be non-native (e.g. Java).
Information and requests are roughly at the level of the Java Virtual Machine Debug Interface (JVM TI), but will include additional information and requests necessitated by bandwidth issues, examples include information filtering and batching.
Java Debug Interface (JDI)
A 100% Java interface implemented by the front-end.
Defines information and requests at a user code level.
While debugger implementors could directly use the Java Debug Wire Protocol (JDWP) or Java Virtual Machine Tool Interface (JVM TI), this interface greatly facilitates the integration of debugging capabilities into development environments. We recommend the JDI layer for all debugger development.
Copyright © 1993, 2017, Oracle and/or its affiliates. All rights reserved.
Tutorial: Debug your first Java application
You have created and run your Java application. Let’s imagine you have discovered that it functions not the way you expected. For example, it returns a wrong value or crashes with an exception. Seems like you have errors in your code, and it’s time to debug it.
What Is Debugging?
Broadly, debugging is the process of detecting and correcting errors in a program.
There are different kinds of errors, which you are going to deal with. Some of them are easy to catch, like syntax errors, because they are taken care of by the compiler. Another easy case is when the error can be quickly identified by looking at the stack trace, which helps you figure out where the error occurred.
However, there are errors which can be very tricky and take really long to find and fix. For example, a subtle logic error, which happened early in the program may not manifest itself until very late, and sometimes it is a real challenge to sort things out.
This is where the debugger is useful. The debugger is a powerful tool, which lets you find bugs a lot faster by providing an insight into the internal operations of a program. This is possible by pausing the execution and analyzing the state of the program by thorough examination of variables and how they are changed line by line. While debugging, you are in full control of the things. In this manual we are covering a basic debugging scenario to get you started.
Examine the code
Let’s try a simple debugging case. Imagine we have the following application:
The program is supposed to calculate the average of all values passed as command-line arguments.
It compiles and runs without issues; however, the result is not what one would expect. For instance, when we pass 1 2 3 as the input, the result is 6.0 .
First of all, you need to think about where the suspected error might be coming from. We can assume the problem is not in the print statements. Most likely, unexpected results are coming from our findAverage method. In order to find the cause, let’s examine its behavior in the runtime.
Set breakpoints
To examine how the program operates at runtime, we need to suspend its execution before the suspected piece of code. This is done by setting breakpoints. Breakpoints indicate the lines of code where the program will be suspended for you to examine its state.
- Click the gutter at the line where the findAverage method is called.
Run the program in debug mode
Now let’s start the program in debug mode.
Since we are going to pass arguments for running and debugging the program, make sure the run/debug configuration has these arguments in place.
- Click the Run icon in the gutter, then select Modify Run Configuration .
- Enter arguments in the Program arguments field.
- Click the Run button near the main method. From the menu, select Debug .
Analyze the program state
After the debugger session has started, the program runs normally until a breakpoint is hit. When this happens, the line where the program paused gets highlighted and the Debug tool window appears.
The highlighted line has not been executed yet. The program now waits for further instructions from you. The suspended state lets you examine variables, which hold the state of the program.
As the findAverage method has not been called yet, all its local variables like result are not yet in scope, however, we can examine the contents of the args array ( args is in scope for the main method). The contents of args are displayed inline where args is used:
You can also get information about all variables that are currently in scope in the Variables panel.
Step through the program
Now that we are comfortable with the Debug tool window, it’s time to step into the findAverage method and find out what is happening inside it.
- To step into a method, click the Step Into button or press F7 .
Another line gets highlighted in the editor because we advanced the execution point one step forward.
- Continue stepping with Step Over F8 . Notice how it is different from Step Into . While it also advances the execution one step forward, it doesn’t visit other methods like Integer.parseInt() along the way. Let’s keep stepping and see how the local variable result is declared and how it is changed with each iteration of the loop.
Right now the variable s contains the value «3» . It is going to be converted to int and be added to result , which currently has the value of 3.0 . No errors so far. The sum is calculated correctly.
- Two more steps take us to the return statement, and we see where the omission was. We are returning result , which has the value of 6.0 , without dividing it by the number of inputs. This was the cause of incorrect program output.
To continue the program execution after it has been suspended, press F9 or select Run | Debugging Actions | Resume from the main menu.
- Let’s correct the error:
Stop the debugger session and rerun the program
In order to check that the program works fine, let’s stop the debugger session and rerun the program.
- Click the Stop button or press Ctrl+F2 .
- Click the Run button near the main method. From the menu, select Run .
- Verify that the program works correctly now.