The Java
Platform Debugger Architecture (JPDA) API included in the JDK lets you
connect to a Java debug session and receive debug events. This code
allows you to do the same things you would normally do with jdb
or an IDE debugger. This is useful if you want to write your own debug,
diagnostics or metrics tools.
Here is some sample code that will be debugged:
import java.util.Random;
public class Test {
int foo;
public static void main(String[] args) {
Random random = new Random();
Test test = new Test();
for (int i = 0; i < 10; i++) {
test.foo = random.nextInt();
System.out.println(test.foo);
}
}
} |
The plan is to monitor changes to the foo member
variable.
NOTE: Sun JDK version 6 is used throughout.
Connecting to the VM
Different implementations provide different mechanisms for
attaching to the VM via connectors.
This allows users to connect by knowing the VM process ID (PID) or a
TCP/IP host name and port, for example.
Here is the console output for performing the task using jdb
on Windows XP:
X:\Debug>start /MIN cmd /C java -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y -cp .\bin Test
X:\Debug>jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8000
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
>
VM Started: No frames on the current call stack
main[1] watch Test.foo
Deferring watch modification of Test.foo.
It will be set after the class is loaded.
main[1] resume
All threads resumed.
> Set deferred watch modification of Test.foo
Field (Test.foo) is 0, will be 346777565: "thread=main", Test.main(), line=37 bci=26
main[1] exit
X:\Debug>
The sequence of commands goes like this:
- Start the
Test main class (the sample code)
minimized in a new console (start /MIN cmd /C) in debug
mode (-Xdebug). Have the VM listen on port 8000 (address=8000).
Wait for the debugger to attach before running (suspend=y).
- Start the debugger and attach to port 8000 (
hostname=localhost,port=8000).
Note the connector (com.sun.jdi.SocketAttach).
- Use
watch Test.foo to start watching the field.
- Use
resume to tell the VM to resume.
- Use
exit to quit the debugger. Note that resume
would have resumed until the field was hit again in the next iteration
of the loop.
Using Java code to connect to the VM
In order to use the JPDA API,
you need to add the JDK tools.jar to the classpath. It can
be found in the JDK lib directory.
To interact with the VM, it is necessary to acquire a VirtualMachine
object instance. The the same connector is used.
Code to connect to the VM:
public class VMAcquirer {
/**
* Call this with the localhost port to connect to.
*/
public VirtualMachine connect(int port)
throws IOException {
String strPort = Integer.toString(port);
AttachingConnector connector = getConnector();
try {
VirtualMachine vm = connect(connector, strPort);
return vm;
} catch (IllegalConnectorArgumentsException e) {
throw new IllegalStateException(e);
}
}
private AttachingConnector getConnector() {
VirtualMachineManager vmManager = Bootstrap
.virtualMachineManager();
for (Connector connector : vmManager
.attachingConnectors()) {
System.out.println(connector.name());
if ("com.sun.jdi.SocketAttach".equals(connector
.name())) {
return (AttachingConnector) connector;
}
}
throw new IllegalStateException();
}
private VirtualMachine connect(
AttachingConnector connector, String port)
throws IllegalConnectorArgumentsException,
IOException {
Map<String, Connector.Argument> args = connector
.defaultArguments();
Connector.Argument pidArgument = args.get("port");
if (pidArgument == null) {
throw new IllegalStateException();
}
pidArgument.setValue(port);
return connector.attach(args);
}
} |
For the sake of brevity I'm assuming that you can figure
out the com.sun.jdi imports yourself.
Monitoring Test.foo with code
Once a VirtualMachine has been acquired, we can use
its EventRequestManager
to instruct it to notify us of events. For example, createClassPrepareRequests
can be used to instruct the VM to notify us when classes are loaded. The
VirtualMachine EventQueue
is then used to process the generated events.
Code that monitors Test.foo:
public class FieldMonitor {
public static final String CLASS_NAME = "Test";
public static final String FIELD_NAME = "foo";
public static void main(String[] args)
throws IOException, InterruptedException {
// connect
VirtualMachine vm = new VMAcquirer().connect(8000);
// set watch field on already loaded classes
List<ReferenceType> referenceTypes = vm
.classesByName(CLASS_NAME);
for (ReferenceType refType : referenceTypes) {
addFieldWatch(vm, refType);
}
// watch for loaded classes
addClassWatch(vm);
// resume the vm
vm.resume();
// process events
EventQueue eventQueue = vm.eventQueue();
while (true) {
EventSet eventSet = eventQueue.remove();
for (Event event : eventSet) {
if (event instanceof VMDeathEvent
|| event instanceof VMDisconnectEvent) {
// exit
return;
} else if (event instanceof ClassPrepareEvent) {
// watch field on loaded class
ClassPrepareEvent classPrepEvent = (ClassPrepareEvent) event;
ReferenceType refType = classPrepEvent
.referenceType();
addFieldWatch(vm, refType);
} else if (event instanceof ModificationWatchpointEvent) {
// a Test.foo has changed
ModificationWatchpointEvent modEvent = (ModificationWatchpointEvent) event;
System.out.println("old="
+ modEvent.valueCurrent());
System.out.println("new=" + modEvent.valueToBe());
System.out.println();
}
}
eventSet.resume();
}
}
/** Watch all classes of name "Test" */
private static void addClassWatch(VirtualMachine vm) {
EventRequestManager erm = vm.eventRequestManager();
ClassPrepareRequest classPrepareRequest = erm
.createClassPrepareRequest();
classPrepareRequest.addClassFilter(CLASS_NAME);
classPrepareRequest.setEnabled(true);
}
/** Watch field of name "foo" */
private static void addFieldWatch(VirtualMachine vm,
ReferenceType refType) {
EventRequestManager erm = vm.eventRequestManager();
Field field = refType.fieldByName(FIELD_NAME);
ModificationWatchpointRequest modificationWatchpointRequest = erm
.createModificationWatchpointRequest(field);
modificationWatchpointRequest.setEnabled(true);
}
} |
The above code is demo code and not very robust, though note it
allows that the Test class might be loaded before or after
the VM is resumed.
Sample output when run against the test class:
com.sun.jdi.SocketAttach
old=0
new=-301449374
old=-301449374
new=913937357
...
I am getting connect refused. Any ideas ?
ReplyDeleteThis is not enough information to resolve the problem.
DeleteConsider posting the steps you have taken to stackoverflow.com along with details of your runtime environment.