📜 ⬆️ ⬇️

Java Agent on 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 so -javaagent:/path-to/plumbr.jar
Although javaagent appeared in java 1.5, many developers never used the capabilities of agents and have a vague idea of ​​what it is.
What is this agent? Why we may need it and how to write your own?

What is javaagent

As I wrote above, javaagent is one of the parameters of the JVM, which allows you to specify the agent that will be launched with your application, or rather, it will be launched before launching your application. The agent itself is a separate application that provides access to the bytecode manipulation mechanism ( java.lang.instrument ) at runtime. This is if in brief. Official documentation can be read here , but it is rather scarce. Nothing is clear? So, let's understand. It is best to understand the examples.

Write elemental agent


 package ru.habrahabr.agent; public class Agent007 { public static void premain(String args) { System.out.println("Hello! I`m java agent"); } } 

Note that the agent must implement the premain method with the following signature.
public static void premain(String args);
or
public static void premain(String args, Instrumentation inst);

The agent class must be packaged in a jar and contain MANIFEST.MF , with the required attribute
PreMain-Class - points to the agent class with the premain method. There are other agent attributes, but they are optional and now we will not need it.

This is what our manifest.mf will look like.
 Manifest-Version: 1.0 PreMain-Class: ru.habrahabr.agent.Agent007 
don't forget to add a newline to the end of the file
')
Now we pack it all in jar
 jar -cvfm Agent007.jar manifest.mf ru/habrahabr/agent/Agent007.class 

Finally, the test class
 package ru.habrahabr.agent; public class AgentTester { public static void main(String[] args) { System.out.println("Hello! I`m agent tester"); } } 

Start 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:

Let's try to extract some benefit from the agent.


In general, the agent mechanism is designed to manipulate bytecode , but I’ll just say that we will not be able to modify the bytecode in this article. Otherwise, you can go far, far away from this post. Who can be interested to look at javassist as there are no standard tools for working with bytecode.

We write AgentCounter which will display the name of the loaded class and count the number of loaded classes. So we can watch the work of the 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()); } } 

Notice now I’m using a different premain method signature. In the instrumentation object, I pass the ClassTransformer which does all the work. ClassTransformer will be triggered every time the class is loaded. If you want to use your ClassTransformer, you must implement the java.lang.instrument.ClassFileTransformer interface and add your object via the Instrumentation.addTransformer method

 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; } } 

The classfileBuffer is the bytecode of the current class represented as an array of bytes. To override it, the transformer must return a new array of bytes. In this example, we do not change the contents of the class, so we simply return the same array.

We pack agent and transformer in a new jar
 jar -cvfm agentCounter.jar manifest.mf ru/habrahabr/agent/AgentCounter.class ru/habrahabr/agent/ClassTransformer.class 

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 {}; 

We start AgentTester with the 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 
results may vary for different versions of java

If you run any enterprise application with such an agent, you can get quite interesting results, for example, one of the projects after the start gave me the following:
 sun.reflect.GeneratedMethodAccessor230 loaded 33597 classes java.rmi.server.Unreferenced loaded 33598 classes 

We measure the size of java objects


Consider another example of using agents. We write a class that will return the size of java objects and the javaagent will play a key role. Whoever, like the JVM, can know the actual size of the created object , in the Instrumentation interface there is a remarkable long getObjectSize(Object objectToSize) that returns the size of the object. But how to get access to the agent from our application? And we don’t have to do anything special, the javaagent is automatically added to the classpath and all we have to do is add an 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 AgentMemoryCounter.getSize(obj) method from an 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<String>()); 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 

Note that the getObjectSize() method does not take into account the size of the nested objects, that is, only the memory spent on the object reference is taken into account.

Conclusion


I hope this post has helped to understand the purpose of javaagent-s for those who have never worked with them, I also tried to demonstrate the alternative use of javaagent (not for byte-code transformation). And why do you use agents in your projects? Write in the comments, it would be very interesting.

Source: https://habr.com/ru/post/230239/


All Articles