Mutant World

Friday, November 04, 2005

Attaching to a Mustang, explained

I was intrigued by this blog, referenced by Eamonn, so I decided to take a look at the "attach on demand" feature.

Since J2SE 5, it is possible to instrument a JVM using an "agent" with the following:

java -javaagent:<jarpath>[=<options>] <mainclass>

where <jarpath> is the path to the jar file containing the agent implementation (absolute, or relative to the directory from where the JVM has been started).
The option can be present multiple times, thus instantiating multiple agents.

The jar's manifest must contain an entry called Premain-Class that specifies the full qualified name of the agent implementation class.
A side effect of invoking the JVM with the -javaagent option is that the jar specified by <jarpath> is added to the system classpath.

Agent implementations must have a method called premain that is called by the JVM before the method main in <mainclass> is called.
In J2SE 5, the signature of premain() must be:
public static void premain(String options, Instrumentation inst)

In J2SE 6 the signature can only be:
public static void premain(String options)

That is, if the instrumentation implementation is not needed, you can drop it from the signature.

In J2SE 6, the agent mechanism has been extended so it is possible to invoke agents after the JVM has been started, and not only at JVM startup using the -javaagent option.

The mechanism is slightly different, and requires use of com.sun.* classes, so it's specific to Sun's (and derived) JVMs.
Let's take a look at how it works.

First, we need a reference to the JVMs we want to attach to. This is accomplished with the following code (classes are from com.sun.tools.attach.* package, shipped in $J2SE6/lib/tools.jar):
List<? extends VirtualMachineDescriptor> jvms = VirtualMachine.list();

Then it's possible to attach to a JVM using:
VirtualMachineDescriptor vmd = ...;
VirtualMachine jvm = VirtualMachine.attach(vmd);

Once we're attached, we can invoke the agent mechanism using:
String jarPath = ...;
String options = ...;
jvm.loadAgent(jarPath, options);

Once the agent is loaded in the target JVM, we can detach from it:
jvm.detach();

Loading the agent with this mechanism has the same side effect of adding the jar specified by jarPath to the system classpath.
The jar's manifest file must contain, this time, and entry called Agent-Class specifying the full qualified name of the agent implementation class.

However, the agent implementation must be slightly different.
Instead of calling the premain method, the JVM calls this method:
public static void agentmain(String options, Instrumentation inst)

Again, if you don't need it, you can drop the Instrumentation argument from the signature.

Inside agentmain() it's possible to write any code (with the same restrictions of premain()), so it's basically possible to do whatever one wants.

For example, using this mechanism in Mustang allows to do, in any moment, the following:
  • add a jar to the system classpath

  • be able to invoke a method of an arbitrary class (the agent)

It is possible to write a completely empty implementation of agentmain() just to be able to load new jars on demand.

Another use is to be able to "hot fix" a running JVM, using the java.lang.instrument.Instrumentation implementation to replace a malfunctioning class (with the restrictions imposed by the instrumentation mechanism).

Or can be used to tell to the target JVM to collect information about what is doing and dump it somewhere for statistical/monitoring purposes.

In Mustang there is an example of this mechanism to start a JMXConnectorServer, so that the JVM becomes remotely manageable. The jar is shipped in $J2SE6/jre/lib/management-agent.jar and it's completely empty (apart the manifest file) since the agent implementation is shipped in rt.jar (it's the sun.management.Agent class).

Feel free to drop a comment if you have other cool ideas of how to use this mechanism !