Implementing a Java Agent (Part 2)

In the first part of this series, I demonstrated how to create a Java agent and attach it to an application during startup. It is also possible to dynamically attach your agent to a running program, as long as you have permissions to access the JVM. The updated source code is available for download.

The first change that we will have to make is to add a new method called “agentmain” to our Agent class that will get executed when we dynamically attach to the JVM:

public class MyAgent {
    // Same as before
    public static void premain(String args, Instrumentation inst) {
        System.out.println("MyAgent start: " + args);
    }
    // New method
    public static void agentmain(String args, Instrumentation inst) {
        System.out.println("MyAgent main: " + args);
    }
}

Note that the arguments which are getting passed to this new method are the same as to the premain method before. Also, I should say that when the agent is loaded dynamically, the premain method will no longer get called – so make sure that any initialization needed by your agent is handled for both use cases.

Next, we need to add the new method to the agent’s JAR manifest by editing its pom-file:

  …
  <configuration>
    <archive>
	  <manifestEntries>
	    <Premain-Class>com.afqa123.example.MyAgent</Premain-Class>
	    <Agent-Class>com.afqa123.example.MyAgent</Agent-Class>
	  </manifestEntries>
	</archive>
  </configuration>
  …

In the project download, you’ll find a new module called MyController. We will use this class to connect to the target JVM and load the agent. Using the VirtualMachine class, we can request a list of all running JVMs which you can access:

public class MyController {
    public static void main(String[] args) {
        // Name of the Java executable to attach to is passed as 1st argument
        // from the command line
        String targetName = args[0];
        // Find the target JVM by name
        VirtualMachineDescriptor targetJvm = null;
        for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) {
            if (descriptor.displayName().startsWith(targetName)) {
                targetJvm = descriptor;
                break;
            }
        }

Once we have a reference to the JVM, we can attach to it and load the agent-jar:

        // Location of the agent-jar is passed as 2nd argument from the 
        // command line
        String agentJar = args[1];
        try {
            VirtualMachine vm = VirtualMachine.attach(targetJvm);
            vm.loadAgent(agentJar, null);
            vm.detach();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

After you build the modules, copy the JAR files for all of them to a single directory of your choice. Then, start the test application from the command line:

> java -jar MyApplication-0.0.1-SNAPSHOT.jar

Now, the moment you’ve all been waiting for: open up a new command line window, and execute the controller application to attach and load the agent:

> java -cp "%JAVA_HOME%/lib/tools.jar;MyController-0.0.1-SNAPSHOT.jar" \
com.afqa123.example.MyController MyApplication-0.0.1-SNAPSHOT.jar \
MyAgent-0.0.1-SNAPSHOT.jar

If you switch over to the first command line window, you should see output from the agent similar to this:

…
Tick
Tock
MyAgent main: null
Tick
Tock

In the next post I will show you how to control the agent by exposing methods using JMX.

 

Author: Ben

1 thought on “Implementing a Java Agent (Part 2)”

  1. Hello, Ben! Thanks for the post, great content! Sometimes we have to invest time in learning, but when we find a post that explains things in a simple and direct way it makes a big difference.

Leave a Reply

Your email address will not be published. Required fields are marked *