Java Agent on the JVM Service

Probably many have heard or encountered such a JVM parameter as -javaagent, you could see this parameter using Jrebel or Plumbr it could look like this
JAVA_OPTS=-javaagent:[path/to/]jrebel.jar
or that, -javaagent:/path-to/plumbr.jar
although javaagent appeared in version java 1.5, many developers have never used the capabilities of agents and have vague an idea of what it is.
What kind of agent is this? Why do we need it and how to write your own?
What is javaagent
As I wrote above, javaagent is one of the JVM parameters that allows you to specify the agent that will be launched with your application, or rather, it will be launched even before your application starts. The agent itself is a separate application that provides access to the bytecode manipulation mechanism (java.lang.instrument
) in runtime. That is, in short. You can read the official documentation here , but it is rather meager. Nothing is clear? So let's get it right. It is best to understand examples.Let's write an elementary agent
package ru.habrahabr.agent;
public class Agent007 {
public static void premain(String args) {
System.out.println("Hello! I`m java agent");
}
}
Please note that the agent must implement the premain method with the following signature,
public static void premain(String args);
or the
public static void premain(String args, Instrumentation inst);
Agent class must be packaged in jar and contain
MANIFEST.MF
, with the required PreMain-Class attribute - points to the agent class with the premain method. There are other attributes of the agent, but they are optional and we will not need them now.
This is what our manifest.mf will look like.
Manifest-Version: 1.0
PreMain-Class: ru.habrahabr.agent.Agent007
do not forget to add the line feed to the end of the fileNow we will pack all this in jar
jar -cvfm Agent007.jar manifest.mf ru/habrahabr/agent/Agent007.class
Finally, the class tester
package ru.habrahabr.agent;
public class AgentTester {
public static void main(String[] args) {
System.out.println("Hello! I`m agent tester");
}
}
Launch AgentTester from the command line
java -javaagent:Agent007.jar ru.habrahabr.agent.AgentTester
Hello! I`m java agent
Hello! I`m agent tester
This example shows that:
- The premain method is executed before the main method of the main application is called.
- agent is specified using the parameter
-javaagent:jarpath[=options]
Let's try to get some benefit from the agent
In general, the agent mechanism is designed to manipulate bytecode , but I’ll say right away that we will modify the bytecode in this article, otherwise we can go far, far beyond the scope of this post. Who cares, you can look at javassist as there are no standard tools for working with bytecode.
We write an AgentCounter which will display the name of the loaded class and count the number of loaded classes. So we can observe the work of classloader`a.
package ru.habrahabr.agent;
import java.lang.instrument.Instrumentation;
public class AgentCounter {
public static void premain(String agentArgument, Instrumentation instrumentation) {
System.out.println("Agent Counter");
instrumentation.addTransformer(new ClassTransformer());
}
}
Please note that now I am using a different premain method signature. In the instrumentation object, I pass a ClassTransformer which does all the work. ClassTransformer will fire every time the class loads. If you want to use your ClassTransformer, you must implement the interface
java.lang.instrument.ClassFileTransformer
and add your object through the methodInstrumentation.addTransformer
package ru.habrahabr.agent;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class ClassTransformer implements ClassFileTransformer {
private static int count = 0;
@Override
public byte[] transform(ClassLoader loader,
String className,
Class classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
System.out.println("load class: " + className.replaceAll("/", "."));
System.out.println(String.format("loaded %s classes", ++count));
return classfileBuffer;
}
}
classfileBuffer - this is the byte code of the current class represented as an array of bytes, to override it, the transformer must return a new byte array, in this example we do not change the contents of the class, therefore we simply return the same array.
Pack the agent and the transformer in the new jar
jar -cvfm agentCounter.jar manifest.mf ru/habrahabr/agent/AgentCounter.class ru/habrahabr/agent/ClassTransformer.class
We slightly modify the tester class
package ru.habrahabr.agent;
public class AgentTester {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = null;
}
}
class A {};
class B {};
class C {};
Launch AgentTester with a new agent
java -javaagent:agentCounter.jar ru.habrahabr.agent.AgentTester
Agent Counter
load class: sun.launcher.LauncherHelper
loaded 1 classes
load class: ru.habrahabr.agent.AgentTester
loaded 2 classes
load class: ru.habrahabr.agent.A
loaded 3 classes
load class: ru.habrahabr.agent.B
loaded 4 classes
for different versions of java, the results may differ.If you run some enterprise application with such an agent, you can get quite interesting results, for example, one of the projects after starting gave me the following:
sun.reflect.GeneratedMethodAccessor230
loaded 33597 classes
java.rmi.server.Unreferenced
loaded 33598 classes
Measuring the size of java objects
Consider another example of using agents. We will write a class that will return the size of java objects and javaagent will play a key role. Whoever, no matter how the JVM can know the real size of the created object ,
Instrumentation
there is a wonderful method in the interface long getObjectSize(Object objectToSize)
that returns the size of the object. But how to get access to the agent from our application? And you don’t have to do anything special, javaagent is automatically added to the classpath and we can only add a Instrumentation instrumentation field to the agent and initialize it in the premain method.package ru.habrahabr.agent;
import java.lang.instrument.Instrumentation;
public class AgentMemoryCounter {
private static Instrumentation instrumentation;
public static void premain(String args, Instrumentation instrumentation) {
AgentMemoryCounter.instrumentation = instrumentation;
}
public static long getSize(Object obj) {
if (instrumentation == null) {
throw new IllegalStateException("Agent not initialised");
}
return instrumentation.getObjectSize(obj);
}
}
We access the method
AgentMemoryCounter.getSize(obj)
from the application class.package ru.habrahabr.agent;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
public class AgentTester {
public static void main(String[] args) {
printObjectSize(new Object());
printObjectSize(new A());
printObjectSize(1);
printObjectSize("string");
printObjectSize(Calendar.getInstance());
printObjectSize(new BigDecimal("999999999999999.999"));
printObjectSize(new ArrayList());
printObjectSize(new Integer[100]);
}
public static void printObjectSize(Object obj) {
System.out.println(String.format("%s, size=%s", obj.getClass()
.getSimpleName(), AgentMemoryCounter.getSize(obj)));
}
}
class A {
Integer id;
String name;
}
The results of the application can look like this
java -javaagent:agentMemoryCounter.jar ru.habrahabr.agent.AgentTester
Agent Counter
Object, size=8
A, size=16
Integer, size=16
String, size=24
GregorianCalendar, size=112
BigDecimal, size=32
ArrayList, size=24
Integer[], size=416
Please note that the method
getObjectSize()
does not take into account the size of nested objects, i.e. only the memory spent on the link to the object is taken into account.Conclusion
I hope this post helped to understand the purpose of javaagents for those who have never worked with them, I also tried to demonstrate an alternative use of javaagents (not for transforming bytecode). And why are you using agents in your projects? Write in the comments, it would be very interesting.