A Java-agent is a
special program which can be used to inspect or modify another Java
program at run time. Java agents were introduced with the Java
Instrumentation package in JDK 1.5.
Java agents are mostly used
for Debugging, Profiling
and Logging a program.
Let's see how Java-agents works by using a simple example
In this example we
are creating a Java-gent which calculate the execution time of a
method and print the time on terminal.
In order to understand this
code, you should be
familiar with following technologies
- Java programming
- Maven build tool
- How to create a jar file (Agent is built as a jar file)
In this example we are creating a calculator program. This calculator program is the class to be instrumented by the Java-agent class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Main { | |
public static void main(String[] args) { | |
System.out.println("Addition : " + addition(30, 6)); | |
System.out.println("Subtraction : " + subtraction(30, 6)); | |
System.out.println("Multiplication : " + multiplication(30, 6)); | |
System.out.println("Division : " + division(30, 6)); | |
} | |
public static int addition(int i, int j) { | |
return i + j; | |
} | |
public static int multiplication(int i, int j) { | |
return i*j; | |
} | |
public static int subtraction(int i, int j) { | |
return i - j; | |
} | |
public static double division(int i, int j) { | |
return (1.0*i)/j; | |
} | |
} |
You can save this code in a file named Main.java and run this class from terminal by using following commands.
javac Main.javaIt will show an output similar to this
java Main
Addition : 36Let's create the Java-agent
Subtraction : 24
Multiplication : 180
Division : 5.0
You need to create 3 files for the Java-agent program.
1. JavaAgent.java
2. pom.xml (Maven)
3. MANIFEST.MF (jar manifest file)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javassist.ClassPool; | |
import javassist.CtClass; | |
import javassist.CtMethod; | |
import java.lang.instrument.ClassFileTransformer; | |
import java.lang.instrument.IllegalClassFormatException; | |
import java.lang.instrument.Instrumentation; | |
import java.security.ProtectionDomain; | |
public class JavaAgent { | |
//This method is called by the jvm once the agent is attached | |
public static void premain(String args, Instrumentation instrumentation) { | |
System.out.println("Instrumentatin Agent Added ..."); | |
instrumentation.addTransformer(new SimpleClassTransformer()); | |
} | |
//This class is added to register as a transformer | |
private static class SimpleClassTransformer implements ClassFileTransformer { | |
//This method is called for every class (Except special classes) before loaded by the JVM | |
public byte[] transform(ClassLoader loader, String className, | |
Class<?> classBeingRedefined, ProtectionDomain protectionDomain, | |
byte[] classfileBuffer) throws IllegalClassFormatException { | |
if(className.equals("Main")) { // We are only interested in the Main class | |
try { | |
// Javassist class pool | |
ClassPool classPool = ClassPool.getDefault(); | |
CtClass ctMainClass = classPool.get("Main"); | |
// get the methods of the Main class as an array | |
CtMethod[] methods = ctMainClass.getMethods(); | |
for(int i = 0 ; i < methods.length; i++) { | |
// Iterate through the array and select the methods that we are interested | |
CtMethod selectedMethod = methods[i]; | |
if(selectedMethod.getName().equals("addition") | |
|| selectedMethod.getName().equals("subtraction") | |
|| selectedMethod.getName().equals("multiplication") | |
|| selectedMethod.getName().equals("division")) { | |
//change the byte code of that method body | |
//add a local variable of long type | |
selectedMethod.addLocalVariable("nanoTime" , CtClass.longType); | |
//initialize the declared variable with the system time (nano seconds) at the beginning of the method | |
selectedMethod.insertBefore("nanoTime = System.nanoTime();"); | |
//print the difference of the time at the end of the method | |
selectedMethod.insertAfter("System.out.println(\"Method execution time in Millis : \" + (1.0*System.nanoTime() - nanoTime)/1000000);"); | |
} | |
} | |
return ctMainClass.toBytecode(); // return the instrumented class | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
return new byte[0]; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Premain-Class: JavaAgent | |
// new line (please remove these characters and keep the new line blank) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<groupId>com.alltechviewer.agent</groupId> | |
<artifactId>JavaAgent</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
<dependencies> | |
<dependency> | |
<groupId>org.javassist</groupId> | |
<artifactId>javassist</artifactId> | |
<version>3.18.0-GA</version> | |
</dependency> | |
</dependencies> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-jar-plugin</artifactId> | |
<version>3.0.2</version> | |
<configuration> | |
<archive> | |
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> | |
</archive> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> | |
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
src | |
|------main | |
| '---java | |
| | '---JavaAgent.java | |
| '---resources | |
| '---META-INF | |
| '---MANIFEST.MF | |
pom.xml |
premain method act as the main method of the java agent. This method is executed by the JVM once the agent is attached. Here we are creating a class named SimpleClassTransformer which implements the ClassFileTransformer interface and it has a method named transform. Once the agent attached every class (Except some bootstrap time classes and some classes upon which JavaAgent depends) is passed through this method before the specific class is loaded by the Java class loader.
Read the comments added in the JavaAgent.java file for more information.
MANIFEST.MF
This file contain the Pre-main class name. JVM find the class name to execute from this file. (There should be a new line at the end of each line of a mainfest file to work properly)
pom.xml
This file contains the dependencies required for the agent program. Here we are using Javassist which is a great library for instrumenting java classes. You can get more information on it through this link. (Javassist)
Instructions to run the program
- Go to the folder containing src folder and the pom.xml file and build the project using mvn package command.
- If the build is successful there will be a new jar file named JavaAgent-1.0-SNAPSHOT.jar in the target folder.
- Now you need to add this jar file as the java-agent of the first program(calculator) and execute it again.
- Go to the folder containing calculator program which we have developed earlier. There should be two files(Main.java, Main.class) in that folder since we have compiled it earlier.
- Now execute the following command.
java -cp "/absolute/path/to/javassist-3.18.0-GA.jar:./" -javaagent:"/path/to/JavaAgent-1.0-SNAPSHOT.jar" Main
- Then you will see an output similar to this
Instrumentatin Agent Added ...This example is shown using a Linux environment if you are on a different operating system then you need to change the terminal commands specific to the relevant operating system. Please share your questions, ideas, & suggestions by commenting below.
Method execution time in Millis : 0.013584
Addition : 36
Method execution time in Millis : 0.001592
Subtraction : 24
Method execution time in Millis : 0.001158
Multiplication : 180
Method execution time in Millis : 0.001265
Division : 5.0