Java agent

A Java agent is a Java program that is loaded by the Java runtime in a special way and can be used to interfere with the byte code of the loaded classes, altering them. They can be used to:

  • List or log, and report the loaded classes during runtime, as they are loaded
  • Modify the classes so that the methods will contain extra code to report runtime behavior
  • Support debuggers to alter the content of a class as the developer modifies the source code

This technology is used in, for example, the products JRebel and XRebel from https://zeroturnaround.com/.

Although Java agents work in the deep details of Java, they are not magic. They are a bit complex and you need a deep understanding of Java, but anyone who can program in Java can write a Java agent. All that is required is that the class, which is the agent, has some predefined methods packaged into a JAR file along with the other classes of the agent and has a META-INF/MANIFEST.MF file that defines the names of the classes implementing the premain and/or agentmain methods, and some other fields.

The detailed and precise reference documentation is part of the JDK JavaDoc available at http://download.java.net/java/jdk9/docs/api/ in the documentation of the java.lang.instrument package.

When a Java application is started with a Java agent, the command line has to contain the following option:

    -javaagent:jarpath[=options]

Here, jarpath points to the JAR file that contains the agent class and the manifest file. The class must have a method named premain or agentmain. It may have one or two arguments. The JVM tries to call the two-argument version first after the JVM is initialized:

public static void premain(String agentArgs, Instrumentation inst);

If a two-argument version does not exist, then the one-argument version is used, which is essentially the same as the two-argument version but misses the instrumentation argument, which, in my opinion, does not make too much sense since a Java agent cannot do much without the Instrumentation object:

public static void premain(String agentArgs);

The agentArgs parameter is the string passed as an option on the command line. The second argument, Instrumentation, provides methods to register class transformers that can modify class byte codes and also methods that can ask the JVM to perform redefinition or retransformation of classes during runtime.

Java applications can also load an agent after the program has already started. In such a case, the agent cannot be invoked before the main method of the Java application, since it has already started by that time. To separate the two cases, JVM calls agentmain in such a scenario. Note that either premain or agentmain is invoked for an agent and never both. A single agent can implement both so that it is capable of performing its task loaded at the startup, specified on the command line or after the JVM started.

If agentmain is used, it has the same arguments as premain.

There is one major and important difference between the invocation of premain and agentmain. If an agent cannot be loaded during startup, for example, if it cannot be found, if the JAR file does not exist, if the class does not have the premain method, or if it throws an exception, the JVM will abort. If the agent is loaded after the JVM is started (in this case, agentmain is to be used), the JVM will not abort if there is some error in the agent.

This approach is fairly reasonable. Imagine that there is a server application that runs on the Tomcat servlet container. When a new version is started, the system is down for a maintenance period. If the new version cannot be started because the agent is not behaving well, then it is better not started. The damage to debug the situation and fix it, or roll back the application to the old version and call for a longer fixing session may be less than starting up the application and not having the proper agent functionality. If the application starts up only without the agent, then the suboptimal operation may not immediately be recognized.
On the other hand, when an agent is attached later, the application is already running. An agent is attached to an already running application to get information from an already running instance. To stop the already running instance and fail it, especially in an operational environment, is more damaging than just not attaching the agent. It may not go unnoticed anyway because the agent that is most probably attached is used by operational personnel.

A premain or agentmain agent gets an Instrumentation object as the second argument. This object implements several methods. One of them is:

void addTransformer(ClassFileTransformer transformer)

The agent implements the transformer, and it has the transform method signature:

byte[] transform(Module module, ClassLoader loader, 
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException

This method is called by the JVM when a class is loaded or when it is to be transformed. The method gets the class object itself, but, more importantly, it gets the byte array containing the byte code of the class. The method is expected to return the byte code of the transformed class. Modifying the byte code needs some knowledge of how the byte code is built and what the structure of a class file is. There are libraries that help to do that, such as Javassist (http://www.javassist.org/ ) or ASM (http://asm.ow2.org/). Nevertheless, I will not start coding before getting acquainted with the structure of the byte code.

Agents, running in a separate thread and presumably interacting with the user or the filesystem and based upon some external observation at any time, may call the following method to perform the retransformation of the classes using the registered transformers:

void retransformClasses(Class<?>... classes)

The agent can also call the following method, which will redefine the classes given as arguments:

void redefineClasses(ClassDefinition... definitions)

The ClassDefinition class is simply a Class and a byte[] pair. This will redefine the classes through the class maintaining mechanism of the JVM.

Note that these methods and Java agents interact with the deep, low-level part of the JVM. This also bears the consequence that it is very easy to destroy the whole JVM. The byte code is not checked, unlike during the loading of the class, and thus, if there is some error in it, the consequence may not only be an exception but also the crashing of the JVM. Also, the redefinition and the transformations should not alter the structure of the classes. They should not change their inheritance footprint, add, rename, or remove methods, or change the signature of the methods, and this is also true for fields.

Also note that the already created objects will not be affected by the changes; they will still use the old definition of the class and only new instances will be affected.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.220.92