Introduction to Java Agents

In this article we will discuss about Java Agents. Java Agents are software components that provide instrumentation capabilities to an application. In the context of agents, instrumentation provides the capability of re-defining the content of class that is loaded at run-time. We will discuss this in more detail in the further sections.

Download Source Code: Java Agents (987)

Writing the first agent

This section details on writing the first java agent. The purpose of the agent will be get as simple as possible as the aim of this section is just to get a bit introduced to the the usage of java agents. Coding a agent requires writing a java class that has the premain() method. Please refer the below code.

also read:

package net.javabeat.articles.javaagent.test;
import java.lang.instrument.Instrumentation;
public class TestJavaAgent {
	public static void premain(String agentArgument,
                   Instrumentation instrumentation){
		System.out.println('Test Java Agent');
	}
}

Note that it is mandatory for agents to have the above method with the exact signature. Note that the above agent does nothing another that printing sample content to the console.

Manifest-Version: 1.0
Premain-Class: net.javabeat.articles.javaagent.test.TestJavaAgent

Agents are packaged as jar files and are made visible to the application using the option ‘javaagent’. Note that it is essential for the jar file to have the Premain-Class attribute that specifies the fully qualified name of the agent class containing the premain method. Now that we have written an agent, we have to make use of the agent. For making use of the agent, let us create a sample java class as follows. Note that nowhere in the code, we are invoking the agent. Instead while running the application, agents are specified as virtual machine arguments.

package net.javabeat.articles.javaagent.test;

public class TestMain {

	public static void main(String[] args) {
		System.out.println('Test Main Class');
	}
}
  • Create a jar file called ‘test-agent.jar’ by packaging the class file ‘net.javabeat.articles.javaagent.test.TestJavaAgent’ and the manifest file ‘MANIFEST.MF’
  • Make use of the command, to run the agent, java -javaagent:test-agent.jar net.javabeat.articles.javaagent.test.TestMain

The above command will produce the below output,

Test Java Agent
Test Main Class

Note that there are several things to be noted in the above section. First of all, the agent to be run is passed as virtual machine parameter to the application. This is done through the option ‘javaagent’, what follows after that is ‘:’ followed by the path to the jar file. Note that agent classes are invoked first before the above application classes, this can be seen from the output produced by the sample program.

Writing a simple Agent

Now that we have familiarized with the several aspects involved in writing a simple agent, let us start adding some more functionality to the agent in this section. Consider the below code snippet representing the agent,

package net.javabeat.articles.javaagent.simple;
import java.lang.instrument.Instrumentation;
public class SimpleAgent {
	public static void premain(String agentArguments,
                      Instrumentation instrumentation){
		System.out.println('Simple Agent');
		SimpleClassTransformer transformer =
                    new SimpleClassTransformer();
		instrumentation.addTransformer(transformer);
	}
}

Note that the very purpose of having agent is to provide instrumentation capabilities to the application – i.e. the capability to re-define the signature of the class files during run-time. The method premain() is passed with Instrumentation object that serves its very purpose. Using Instrumentation object it is possible to adding transformer objects. Transformer object does the real job of transforming (or re-defining) the content of class files at run-time. The above code defines a transformer object, whose declaration is given below,

package net.javabeat.articles.javaagent.simple;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class SimpleClassTransformer implements ClassFileTransformer{
	public byte[] transform(ClassLoader    loader,
            String              className,
            Class            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer)
			throws IllegalClassFormatException {

		System.out.println(className);
		return classfileBuffer;
	}
}

Note that, a transformer object must implement the ClassFileTransformer interface and the abstract method transform needs to be overridden. This method will be called for every class that is loaded as part of the application. Note that the content of the class can be represented as byte array and this method can re-define the class content that returnss a different byte array (as denoted by the method signature). For simplicity, this method returns the original class byte array that is passed to it.

Manifest-Version: 1.0
Premain-Class: net.javabeat.articles.javaagent.simple.SimpleAgent

Given above is the content of the manifest file for the agent. Note that for testing the agent, we need a sample application file which is given below. To illustrate the behavior of class loading, the application creates instances for Test classes.

package net.javabeat.articles.javaagent.simple;
public class SimpleMain {
	public static void main(String[] args) {
		Test1 one = new Test1();
		Test2 two = new Test2();
		System.out.println('Simple Main ' + one + two);
	}
}
class Test1{}
class Test2{}

Running the above application along with the agent will produce the following output.

Simple Agent
net/javabeat/articles/javaagent/simple/SimpleMain
net/javabeat/articles/javaagent/simple/Test1
net/javabeat/articles/javaagent/simple/Test2
Simple Main net.javabeat.articles.javaagent.simple.
Test1@61de33net.javabeat.articles.javaagent.simple.Test2@14318bb

Agents for collecting statistical data

In the last section, we have seen how to make use of Instrumentation object. We also saw how to manipulate Instrumentation object by adding Transformer objects. In this final section, we will see the real capability of agents for providing some statistical information about the methods being loaded for a particular class. For the purpose of illustration, let us expand the scope of the sample by provide a business class having empty business methods. Let us also assume that because the methods are for external usage, all the methods have to be public.

package net.javabeat.articles.javaagent.statistics;
public class MyBusinessClass {
	public void bizMethod1(){
		System.out.println('Biz Method 1');
	}
	@SuppressWarnings('unused')
	private void bizMethod2(){
		System.out.println('Biz Method 2');
	}
}

The above code has the definition of the business class. Deliberately, the modifier for the method bizMethod2() is given as private. The agent class is given below. Note that, as illustrated in the last section, this class makes use of Instrumentation object to add transformer objects to it.

package net.javabeat.articles.javaagent.statistics;
import java.lang.instrument.Instrumentation;
public class StatisticsAgent {
	public static void premain(String agentArguments,
                         Instrumentation instrumentation){
		StatisticsClassTransformer transformer =
                   new StatisticsClassTransformer();
		instrumentation.addTransformer(transformer);
	}
}

The flavor of the Transformer class that has the functionality of providing statistical information is given below. At this point, note that by the time when the agent is invoked, the class is not loaded. It is only during the class loading time, the agent is invoked. Hence reflection related APIs cannot be used at this point. For querying the method information for a particular class, the implementation makes use of ASM (byte code analysis library).

package net.javabeat.articles.javaagent.statistics;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.List;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

public class StatisticsClassTransformer implements ClassFileTransformer{

	public byte[] transform(ClassLoader    loader,
            String              className,
            Class            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer)
			throws IllegalClassFormatException {

		System.out.println();
		System.out.println('Processing class ' + className);

		String normalizedClassName = className.replaceAll('/', '.');

		ClassReader classReader = null;
		try {
			classReader = new ClassReader(normalizedClassName);
		} catch (IOException e1) {
			e1.printStackTrace();
		}

		ClassNode classNode = new ClassNode();
		classReader.accept(classNode, ClassReader.SKIP_DEBUG);

		@SuppressWarnings('unchecked')
		List allMethods = classNode.methods;
		for (MethodNode methodNode : allMethods){
			System.out.println(methodNode.name);
		}
		return classfileBuffer;
	}

	private static void processBizMethods(Class classObject) {
		if (MyBusinessClass.class.equals(classObject)){
			Method[] allMethods = classObject.getDeclaredMethods();
			for (Method aMethod : allMethods){
				System.out.println(aMethod.getName());
				int modifiers = aMethod.getModifiers();
				if (Modifier.isPrivate(modifiers)){
					System.out.println('Method ' +
                                        aMethod.getName() + ' is private');
				}
			}
		}
	}

	public static void main(String[] args) {
		processBizMethods(MyBusinessClass.class);
	}
}

The content of the manifest file for the above agent is given below.

Manifest-Version: 1.0
Premain-Class: net.javabeat.articles.javaagent.statistics.StatisticsAgent

The content of the application that triggers the agent class is given below.

package net.javabeat.articles.javaagent.statistics;
public class StatisticsMain {
	public static void main(String[] args) {
		MyBusinessClass object = new MyBusinessClass();
		System.out.println('Biz Object ' + object);
	}
}

The output of the application is given below,

Processing class net/javabeat/articles/javaagent/statistics/StatisticsMain
<init>
main

Processing class net/javabeat/articles/javaagent/statistics/MyBusinessClass
<init>
bizMethod1
bizMethod2
Biz Object net.javabeat.articles.javaagent.statistics.MyBusinessClass@a59698

Conclusion

Download Source Code: Java Agents (987)

This article provides introductory concepts about Java agents which are introduced from 5.0. It provides a good starter about the usage of agents by providing lot of examples. Hope the readers will be benefited after reading this article.

Comments

comments

About Krishna Srinivasan

He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.

Comments

  1. manish shah says:

    Excellent explanation of java agent.. Nice Job..

  2. Very informative article. Thanks for sharing!

Trackbacks

  1. [...] Writing agents in Java to instrument the class files/byte code. [...]

  2. JavaPins says:

    Introduction to Java Agents…

    Thank you for submitting this cool story – Trackback from JavaPins…

  3. [...] Agent: http://www.javabeat.net/2012/06/introduction-to-java-agents/ Posted in JMX | Tagged ActiveMQ, Firewall, JavaAgent, [...]

Speak Your Mind

*