X

The Attach API

by John Zukowski

When working with the Java platform, you typically program with the
standard java.\* and javax.\* libraries. However,
those aren't the only things that are provided for you with Sun's Java Development Kit (JDK). Several additional APIs are provided in a tools.jar file, found in the lib directory under your JDK installation directory. You'll find
support for extending the javadoc tool and an API called the Attach
API.

As the name may imply, the Attach API allows you to attach to a target
virtual machine (VM). By attaching to another VM, you can monitor
what's going on and potentially detect problems before they
happen. The Attach API classes are found in the com.sun.tools.attach
and com.sun.tools.attach.spi packages, though you'll typically never
directly use the com.sun.tools.attach.spi classes.

Even including the one class in the .spi package that you won't use,
the whole API includes a total of seven classes. Of that, three are
exception classes and one a permission. That doesn't leave much to
learn about, only VirtualMachine and its associated
VirtualMachineDescriptor class.

The VirtualMachine class represents a specific Java virtual machine
(JVM) instance. You connect to a JVM by providing the VirtualMachine
class with the process id, and then you load a management agent to do
your customized behavior:

VirtualMachine vm = VirtualMachine.attach (processid);
String agent = ...
vm.loadAgent(agent);

The other manner of acquiring a VirtualMachine is to ask for the list
of virtual machines known to the system, and then pick the specific
one you are interested in, typically by name:

String name = ...
List vms = VirtualMachine.list();
for (VirtualMachineDescriptor vmd: vms) {
if (vmd.displayName().equals(name)) {
VirtualMachine vm = VirtualMachine.attach(vmd.id());
String agent = ...
vm.loadAgent(agent);
// ...
}
}

Before looking into what you can do with the agent, there are
two other things you'll need to consider. First, the loadAgent method
has an optional second argument to pass settings into the agent.
As there is only a single argument here to potentially pass multiple
options, multiple arguments get passed in as a comma-separated list:

vm.loadAgent (agent, "a=1,b=2,c=3");

The agent would then split them out with code similar to the
following, assuming the arguments are passed into the agent's args
variable.

String options[] = args.split(",");
for (String option: options)
System.out.println(option);
}

The second thing to mention is how to detach the current virtual machine
from the target virtual machine. That's done via the detach
method. After you load the agent with loadAgent, you should call
the detach method.

A JMX agent exists in the management-agent.jar file that comes with
the JDK. Found in the same directory as tools.jar, the JMX management
agent allows you to start the remote JMX agent's MBean Server and get
an MBeanServerConnection to that server. And, with that, you can list
things like threads in the remote virtual machine.

The following program does just that. First, it attaches to the
identified virtual machine. It then looks for a running remote JMX
server and starts one if not already started. The management-agent.jar
file is specified by finding the java.home of the remote virtual
machine, not necessarily the local one. Once connected, the
MBeanServerConnection is acquired, from which you query the
ManagementFactory for things like threads or ThreadMXBean as the case may be. Lastly, a list of thread names and their states are displayed.

import java.lang.management.\*;
import java.io.\*;
import java.util.\*;
import javax.management.\*;
import javax.management.remote.\*;
import com.sun.tools.attach.\*;
public class Threads {
public static void main(String args[]) throws Exception {
if (args.length != 1) {
System.err.println("Please provide process id");
System.exit(-1);
}
VirtualMachine vm = VirtualMachine.attach(args[0]);
String connectorAddr = vm.getAgentProperties().getProperty(
"com.sun.management.jmxremote.localConnectorAddress");
if (connectorAddr == null) {
String agent = vm.getSystemProperties().getProperty(
"java.home")+File.separator+"lib"+File.separator+
"management-agent.jar";
vm.loadAgent(agent);
connectorAddr = vm.getAgentProperties().getProperty(
"com.sun.management.jmxremote.localConnectorAddress");
}
JMXServiceURL serviceURL = new JMXServiceURL(connectorAddr);
JMXConnector connector = JMXConnectorFactory.connect(serviceURL);
MBeanServerConnection mbsc = connector.getMBeanServerConnection();
ObjectName objName = new ObjectName(
ManagementFactory.THREAD_MXBEAN_NAME);
Set<ObjectName> mbeans = mbsc.queryNames(objName, null);
for (ObjectName name: mbeans) {
ThreadMXBean threadBean;
threadBean = ManagementFactory.newPlatformMXBeanProxy(
mbsc, name.toString(), ThreadMXBean.class);
long threadIds[] = threadBean.getAllThreadIds();
for (long threadId: threadIds) {
ThreadInfo threadInfo = threadBean.getThreadInfo(threadId);
System.out.println (threadInfo.getThreadName() + " / " +
threadInfo.getThreadState());
}
}
}
}

To compile this program, you need to make sure you include tools.jar
in your CLASSPATH. Assuming JAVA_HOME is set to the Java SE 6 installation directory, of which the Attach API is a part, the following line will compile your program:

> javac -cp %JAVA_HOME%/lib/tools.jar Threads.java

From here, you could run the program, but there is nothing to attach
to. So, here's a simple Swing program that displays a frame. Nothing
fancy, just something to list some thread names you might recognize.

import java.awt.\*;
import javax.swing.\*;
public class MyFrame {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}

Run the MyFrame program in one window and be prepared to run the
Threads program in another. Make sure they share the same runtime
location so the system can connect to the remote virtual machine.

Launch MyFrame with the typical startup command:

> java MyFrame

Then, you need to find out the process of the running application.
That's where the jps command comes in handy. It will list the process
ids for all virtual machines started for the JDK installation directory
you are using. Your output, specifically the process ids, will
probably be different:

> jps
5156 Jps
4276 MyFrame

Since the jps command is itself a Java program, it shows up in the
list, too. Here, 4276 is what is needed to pass into the Threads
program. Your id will most likely be different. Running Threads then
dumps the list of running threads:

> java -cp %JAVA_HOME%/lib/tools.jar;. Threads 4276
JMX server connection timeout 18 / TIMED_WAITING
RMI Scheduler(0) / TIMED_WAITING
RMI TCP Connection(1)-192.168.0.101 / RUNNABLE
RMI TCP Accept-0 / RUNNABLE
DestroyJavaVM / RUNNABLE
AWT-EventQueue-0 / WAITING
AWT-Windows / RUNNABLE
AWT-Shutdown / WAITING
Java2D Disposer / WAITING
Attach Listener / RUNNABLE
Signal Dispatcher / RUNNABLE
Finalizer / WAITING
Reference Handler / WAITING

You can use the JMX management agent to do much more than just list
threads. For instance, you can call the findDeadlockedThreads method
of ThreadMXBean to find deadlocked threads.

Creating your own agent is actually rather simple. Similar to how
applications require a main method, agents have an agentMain method. This isn't a part of any interface. The system just knows to look for one with the right argument set as parameters.

import java.lang.instrument.\*;
public class SecretAgent {
public static void agentmain(String agentArgs,
Instrumentation instrumentation) {
// ...
}
}

To use the agent, you must then package the compiled class file into
a JAR file and specify the Agent-Class in the manifest:

Agent-Class: SecretAgent

The main method of your program then becomes a little shorter than
the original Threads program since you don't have to connect to the
remote JMX connector. Just be sure to change the JAR file reference
there to use your newly packaged agent. Then, when you run the
SecretAgent program, it will run the agentmain method right at startup, even before the application's main method is called. Like Applet, there are other magically named methods for doing things that are not part of any interface.

Use the Threads program to monitor more virtual machines and try to
get some threads to deadlock to show how you can still communicate
with the blocked virtual machine.

See the Attach API documentation for more information. Consider also reading up on the Java Virtual Machine Tool Interface (JVM TI). It requires the use of the Attach API.

Join the discussion

Comments ( 5 )
  • Frauke Stommel Thursday, August 23, 2007

    This comment is related to an older blog posting at http://blogs.sun.com/CoreJavaTechTips/entry/creating_zip_and_jar_files

    You might want to check out http://corejava-technologies.blogspot.com/2007/08/java-technologies-tech-tips.html where some "blogger" ripped off the article and posted it as his/her own. This has been reported to Google (aka blogspot.com, aka blogger.com) but they apparently don't care. Maybe a friendly reminder from a Sun corporate lawyer would gets Google's attention.


  • Bruce Chapman Sunday, August 26, 2007

    The source line

    Set mbeans = mbsc.queryNames(objName, null);

    should declare the type as Set<ObjectName> not a raw Set.

    This might be an html formatting issue rather than a coding issue tho'.


  • John O'Conner Monday, August 27, 2007

    Bruce, good catch. You are right...it was an html formatting issue. Changing the embedded less-than and greater-than characters to character references helped.


  • Tarek Yassin Monday, August 27, 2007

    This article explains how you can the Attach API in JDK6.0


  • J6 Monday, August 27, 2007

    Good One


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
Oracle

Integrated Cloud Applications & Platform Services