Understanding Java Agents
Understanding Java Agents
A powerful tool you might have missed.
Join the DZone community and get the full member experience.Join For Free
Let’s say that you have an application running in production. Every once in a while, it gets into a broken state, the error is hard to reproduce, and you need some more information out of the application.
So are you wondering about the solution?
What you could do is dynamically attach some set of code to your application and carefully rewrite it so that the code dumps additional information you can log, or else you can dump the application stage into a text file. Java gives us a facility to do this using Java Agent.
Have you ever wondered how our Java code is hot-swapped in our IDE? It’s because of agents. Another interesting fact about Java agent is Application profilers are using the same technique at the backend to collect information of memory usage, memory leakage, and execution time of methods.
So what is a Java Agent?
Java agents are a special type of class which, by using the Java Instrumentation API, can intercept applications running on the JVM, modifying their bytecode. Java agents are extremely powerful and also dangerous.
Before diving in, I will explain how a Java Agent intercepts a Class using the simple HelloWorld Example.
As shown in the below diagram, Classloaders are in charge of loading classes from binary to in-memory. When you run the compiled HelloWorld application (HelloWorld.class), the agent could be viewed as a way to intercept classloaders behavior at runtime. You may think how come the java byte code will be restructured so that agent can add the relevant code at the correct places. The interesting fact is, for Java programs, the structure of the bytecode is really close to the original Java program source code. Hence, while we don’t instrument the Java program itself, we use a very close representation of it. One thing to note is that there are non-Java languages that compile into Java bytecode (such as Scala, Clojure, and Kotlin), which means that the structure and shape of the bytecode for programs can be very different.
Implementing a Java Agent
Java agents are based on facility, coming from the Java platform and the entry point to that is a
java.lang instrument package, which provides services that allow agents to instrument programs running on the JVM. The package is quite simple and self-contained, as it contains a couple of exception classes, a data class, the class definition, and two interfaces. Out of those two, we only need to implement
classFileTransformer interface, if we want to write a Java agent.
There are two ways to define an Agent.
The first one is a static agent, which means that we build our agent we package it as a jar file, and when we start our Java application, we pass in a special JVM argument called
javaagent. Then we give it the location of the agent jar on disk, and then the JVM does its magic.
We need to add a special manifest entry, which is called the pre-main class, and of course, this is a fully qualified name class definition.
The class looks something like this
premain method takes two arguments:
agentArgs— String arguments, whatever the user has chosen to pass as arguments to the Java agent invocation.
instrumentationis from the java.lang instrument package, and we can add a new
ClassFileTransformerobject, which contains the actual logic of our Agent.
The second option is called a dynamic agent.
Instead of instrumenting the way you launch the application, what you can do is write a small piece of code that takes and connects to an existing JVM and tells it to load a certain agent.
agentFilePath is the exact same one as in the static agent approach. It has to be the file name of the agent jar so no input streams no bytes. There are two caveats with this approach. The first one is that this is private API living under the com sun space and it usually works for hotspot implementations. The second one is that sorting with java 9 you can no longer use this code to attach to the JVM it’s running.
This is the interface that we need to implement for our agent in order to transform the classes.
It’s a bit of a mouthful, but I will explain the necessary arguments in the method signature. The first important one is the
className the primary purpose of this parameter is to help to find and differentiate between the right class you want to intercept and others. Obviously, you may not want to intercept each class in your application, and the simplest way to do that is to check with the conditional statement.
ClassLoader, which is mostly used in environments that don’t have a flat class space for basic applications you probably can get away without looking at it, but as soon as you run into something more complicated or a modular platform you need to look in the ClassLoader.
classfileBuffer is the current definition of the class before being instrumented. To intercept it, you need to read this byte array using libraries and intercept your code and then have to transform back to bytecode again to return.
There are several byte code generation libraries. You need to do the research and decide for yourself in terms of whether it is a high-level API or low-level API, community size, and the license. The demo I put below is Javassist because I think it has a nice balance between high-level and low-level API s and also is a triple license so it should be available for almost anyone to consume. so this is the body of the implementation of the
Here, from the
classPool, we can directly get the class bypassing the
classfileBuffer since I wanted to work with the method
main. We loop through all the method in the class definition and get the class we wanted. We do not have to work with bytecode at all. We can simply pass it some legal Java code, and then Javassist will compile it generates the new bytecode and give us that definition.
There are three ways to insert some Java code into the method.
insertAfter(..) inserts bytecode at the end of the body. It inserts bytecode at the end of the body.
insertAt(..) inserts bytecode at the specified line in the body and
insertBefore(..) inserts bytecode at the beginning of the body.
Getting hands-on with Java Agent
- Download the sample application and Java Agent from the link pointed out.
- Build both the repo using going into the path and execute the command
mvn clean install
- Now, you will get the jar files in the target. Copy the path of the
.jarfile in Example Application and copy the path of the
-dependencies.jarfile in JavaAgent.
- First, run the application only with the Example Application using the command
$ java -jar <path of the packaged jar>and observe the output.
Hi I am main.will be printed in the console.
- Then, run the application attached with the java agent using the command
$ java -javaagent:<path of agent jar file> -jar <path of the packaged jar
file you want to intercept>and observe the output.
Logging using Agentwill be printed additionally in the console. This ensures that the java agent has been intercepted and added to the body of the
In summary, If you want to implement a Java Agent:
- You need to create two Java classes. One with the with
premainmethod (JavaAgent) and another class which extends the
- Inside the body of the
premainmethod, you need to add the object of the class which extends the
- Then you need to add the logic inside the overridden method
- When transforming the bytecode inside the transform method you may need to use the bytecode generation libraries according to your purpose.
- You need to specify the
premainclass in the Manifest and build the jar.
- Use the
javaagenttag to load the agent with the application you wanted to intercept.
Me and Java Agent
I am developing some sort of debugger for WSO2 Identity Server, which gets the important variables from the Authentication flow from the server. As I mentioned, in the beginning, It is impossible to change the whole code that we want to intercept. So It is easy to dynamically attach some set of code to Server and carefully rewrite it so that the code fires the additional information you can use to debug. This architecture which does debug without starting the Java Debugging or any code manipulation was amazed me, so I thought of putting some words about this amazing tool.
In this post, we’ve looked at the extremely powerful entry in the Java developer toolbelt: the Java agent. which has the power to access to classes loaded into the JVM. You might wonder if all that we have done too much work for little result. The answer would be a firm “No.” First, you must keep in mind that Hello world example is elaborated here to explain the use of the Java agents. Things that can be done with java agent is enormous and they come handy when it’s complex code to rewrite. I have only just scratched the surface of what can be achieved with java agent, but hopefully, after reading this post, you will now know of their existence and can investigate things further. However, For persistence and proper monitoring, building a reliable java agent is a task that needs to be tackled by a team of dedicated engineers. Let me know how you got on!
Happy blogging! Happy Coding :)
Opinions expressed by DZone contributors are their own.