Executing Diagnostic Commands Using Attach

Sometime it can be very useful to have programmatic access to the Diagnostic Commands available in the Oracle JRE. This is of course both highly dependent on the Oracle JDK and very unsupported, so, as usual, please use responsibly.

Here comes the usual disclaimer:

The following blog entry will describe UNSUPPORTED functionality. This means that relying on the described APIs or functionality may BREAK your code/plugin with any given update of the JDK and/or Mission Control.

The Attach API

There is an API that allows a Java process to attach to another Java process. This is the API used by several different Java tools to both enumerate the locally running Java processes, as well as do things like start up the management agent. The API is usually just called the Attach API, and one of the entry points is the com.sun.tools.attach.VirtualMachine class.

You can use the VirtualMachine class to enumerate all the locally running Java processes (with the same effective user as the process doing the listing) on the machine like this:

List<VirtualMachineDescriptor> vmList = VirtualMachine.list();
for (VirtualMachineDescriptor descriptor : vmList) {
    System.out.println(String.format(“%s %s”, descriptor.id(), descriptor.displayName()));
}

This is how you attach to a running Java process:

VirtualMachine.attach(<PID>); 

<PID> is the process ID as a String. The reason it is a String is probably to not assume that all Operating Systems use integer values for their process identifiers. I guess having the identifier as a String could also come in handy when addressing Isolates/MVM and other more complicated scenarios where what constitutes the “Java Process” becomes a bit more blurry. Anyways, once you have your VirtualMachine there is all kinds of fun you can do, like loading Java agents and agent libraries etc. The most important thing, however, is to detach. Never forget to detach, or you will start leaking process handles.

Invoking Diagnostic Commands

To invoke diagnostic commands, we will need to cast our VirtualMachine to a HotSpotVirtualMachine. Here is how to execute the Thread.print DiagnosticCommand:

VirtualMachine vm = VirtualMachine.attach(<PID>);
HotSpotVirtualMachine hsvm = (HotSpotVirtualMachine) vm;
hsvm.executeJCmd(“Thread.print”);

Or, here as a compilable example:

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import sun.tools.attach.HotSpotVirtualMachine;

import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

public class DiagnosticCommandExample {

    public static void main(String[] args) throws AttachNotSupportedException,
            IOException {
        if (args.length == 0) {
            System.out.println(“Usage: InvokeDiagnosticCommand <PID>”);
            System.exit(2);
        }
        VirtualMachine vm = VirtualMachine.attach(args[0]);
        if (!(vm instanceof HotSpotVirtualMachine)) {
            System.out.println(“Only works on HotSpot!”);
            System.exit(3);
        }
        HotSpotVirtualMachine hsvm = (HotSpotVirtualMachine) vm;
        System.out.println(readInputStreamAsString(hsvm.executeJCmd(“Thread.print”)));
    }

    public static String readInputStreamAsString(InputStream in)
            throws IOException {
        BufferedInputStream bis = new BufferedInputStream(in);
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        int result = bis.read();
        while (result != -1) {
            byte b = (byte) result;
            buf.write(b);
            result = bis.read();
        }
        return buf.toString();
    }
}

Summary

Using the attach API is not supported and the APIs may change at any time. That said, it can still be quite useful for putting together small utilities.

Clarification: Alan Bateman pointed out to me that whilst linking to the sun.tools.attach.HotSpotVirtualMachine class is indeed unsupported, the Attach API in itself is supported and documented.