Java agents work at the lowest level by providing services that enable us to intrude into a running Java program in JVM. This powerful yet uncanny part of Java has the capability to crash JVM if done incorrectly. This article glimpses into the concept and gives an introduction on how it works.
Overview
The classes that represent Java agents are apparently nothing more than any other classes found in the Java API Library. But, what make them special is that they follow a certain convention which empowers Java code to intercept another application running in JVM. The purpose is simply to make agents that investigate or modify the bytecode. This is a powerful yet an uncanny feature that goes beyond of what Java program normally does. In a way, it can break into a program and modify the bytecode or create havoc. Understand that this is not a new technology or feature added to Java. It has been part of the library since JDK 1.5. This means that there are some real benefits to using them as well. But, before addressing their benefits and how to use them, let’s find out where we can find them in Java.
Java Agent and Instrumentation
Java agents are part of the Java Instrumentation API. The Instrumentation APIs provide a mechanism to modify bytecodes of methods. This can be done both statically and dynamically. This means that we can change a program by adding code to it without having to touch upon the actual source code of the program. The result can have a significant impact on the overall behavior of the application.
Java agent and instrumentation APIs are found in the package called java.lang.intrumentation.
Uses of Java Agents
There can be numerous uses of Java agents, such as Aspect Oriented Programming (AOP), Mutation Testing, Profiling, and so forth. AOP typically adds behavior such as logging or security to an existing program without altering the code. It uses Java agents to manipulate the bytecode and has a combined effect of its feature with the program. Monitoring JVM level parameters like object creation, garbage collection, thread execution, and so on, is the job of the profiler. Profiling tools significantly use Java agents in profiling JVM parameters of program in execution.
There are many other situations where Java agents along with the instrumentation API comes quite handy.
How to Write Java Agents
The class that implements Java agent must implement a method called
public static void premain(String agentArgs, Instrumentation inst)
This method forms the entry point of the agent, much like the entry point to a regular Java program is the main method.
After initialization of JVM, the premain method is invoked; this represents the agent. There can be several such agents; therefore, each of the premain methods is invoked according to the order of the agents specified during JVM initialization. If a specific premain method is not found, JVM in turn invokes the overloaded version of the premain method such as
public static void premain(String agentArgs)
The agent class may also contain a method typically used by JVM after agent has started, such as
public static void agentmain(String agentArgs, instrumentation inst)
or, its overloaded version
public static void agentmain(String agentArgs)
This is the typical routine of the JVM and once this routine is complete, main the method is called.
Another important thing is that Java agents must include a MANIFEST.MF file in the META-INF folder of the resource directory during development. This file contains metadata information about package distribution. This file is included as a part of its JAR packaging. The attributes included in the MANIFEST.MF files give a clue as to why this is required. The attributes are as follows:
- Premain-class: This attribute defines the agent class. JVM will abort if this attribute is not defined.
- Agent-class: This defines the mechanism to start Java agents after JVM has started. The agents will not start if this attribute is undefined.
- Can-Redefine-Classes: This defines the ability to redefine classes by the agent. The value can be true or false.
- Can-Retransform-Classes: This defines the ability to retransform classes by the agent. The value can be true or false.
- Can-Set-Native-Method-Prefix: This defines the ability to set a native method prefix by the agent. The value can be true or false.
- Boot-Class-Path: This defines the bootstrap class loader’s search path list.
A Simple Example
Profiler tools often report about different parameters of the Java objects at runtime by extracting information from the JVM. These parameters include information about the memory usage of an object, and the like, by using the instrumentation framework.
- Here we create an agent class with the premain method.
- The Instrumentation instance passed to the premain method will give the information about the object size.
- Package the agent class in a JAR file along with the MANIFEST.MF file.
- Pass the agent to the JVM using command line arguments.
This is a sample class that we will use in our example. There’s nothing special about it.
package com.mano.examples; public class Main { public static void greet(String msg){ System.out.println(msg); } public static void main(String[] args){ greet("Hello Agents"); } }
Agent Class
The instrumentation agent class with the premain method is used to retrieve the information we need. The implementation of the Instrumentation interface is passed to the premain method. We use the getObjectSize method defined by the instrumentation interface to get memory usage of the Main object at runtime.
package com.mano.examples; import java.lang.instrument.Instrumentation; public class MyAgentClass { public static void premain(String agentArgs, Instrumentation inst) { System.out.println(inst.getObjectSize (new Main())) } }
After that, we must create a MANIFEST.MF file. This is nothing but a text file where we put information related to the agent class. JVM will use it to load the agent. The file is typically stored in the META-INF directory. The content required for our example is pretty basic:
Manifest-Version: 1.0 Premain-Class: com.mano.examples.MyAgentClass
Now, compile all the Java files to create the class file. And finally, create the JAR files as follows:
jar -cmf META-INF/MANIFEST.MF myagent.jar com/mano/examples/ MyAgentClass.class
Deploying Java Agents
Once an agent is created, it is deployed as a JAR file. The attribute in the manifest file specifies the agent class which will be loaded to start the agent. Note that there are many ways to start an agent: using the command-line, at runtime, or as JAR executables. We’ll use command-line here.
Running the Agent Using the Command-line
The command-line is:
java -javaagent:myagent.jar -cp . com.mano.examples.Main
This indicates that the premain method will run prior to application execution and the size of the instance of Main is created.
Conclusion
The power provided by the instrumentation API is open to numerous innovations. AOP is a simple example. Although Java agents and Java Instrumentation APIs are not be very frequently used in application development, the idea of what it is all about can clarify many other aspects of Java. The code example given here is rudimentary, just to give an idea how agents are created. The Java API Documentation elaborates on many aspects with greater detail; refer to it for more information.