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.jaror 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 file

    Now 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.ClassFileTransformerand 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 , Instrumentationthere 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.

    Also popular now: