This project shows how to use the Java instrumentation API to add some runtime monitoring to an already created and running application using byte code generation.
Open the project in IDEA, and then you will see the following run configurations.
Main <no args>
- This simply starts theapplication.Main
class and you will see usage instructions printed to the console.Main start 10000 10 1000
- This actually starts the main application which does the following:- Makes an ATM withdrawal for
10
units. - Sleeps for
10_000
ms (this is your chance to attach the agent to see it instrument the ATM). - Makes a 2nd ATM withdrawal for
1000
units.
- Makes an ATM withdrawal for
Main loadagent
- This actually starts the main application and tells it to load the agent, which will attach to an already existing running VM that has executedstart
(above).
So, to play with it, make sure that you build the entire project first. There's a maven task that runs everytime
the project is rebuilt called install
. You can see this in the Maven tool window.
- The first thing you can do is run the
Main <no args>
run configuration just to see the command line usage of theapplication.Main
class. - Then you can run the
Main start...
configuration and it will perform one ATM withdrawal and wait for 10 sec. - Then you can run the
Main loadagent
configuration and it will attach to the VM started above, and will instrument the ATM code by adding some byte code generated usingjavassist
. You can see the output of this byte code injection into your ATM class and it will dump some instrumentation output to the console.
This project is really broken out into two main parts.
application
package - this is the code for the application, the ATM, and the agent loader. The instrumentation code and byte code generation does not exist in theapplication
package. You can run that indepedently of the instrumentation code, by simply runningApp#main()
.agent
package - the code here is used to generate bytecode and use the instrumentation API. This code is what is built by Maven along w/ theMANIFEST.MF
file into a JAR file (target/java-instrumentation-agent-0.1.0-SNAPSHOT.jar
). This JAR file is loaded by theapplication.AgentLoader
into the VM that is running theMain start...
run configuration (theapplication
package).
There is a MANIFEST.MF
file that is in the resources/META-INF
folder which is used by Maven to generate the
agent JAR file. In IDEA, in the Maven tool window, you will find that the install
task is run after a full rebuild
of the project in IDEA. Here's the code from the pom.xml
that does this.
<build>
<plugins>
<!-- Agent manifest. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven.jar.plugin.version}</version>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
</build>
Also, it was useful to use the following command to debug the maven install
task from the command line, which
generates some really helpful debug messages during compilation and packaging the agent JAR file.
mvn clean install -X
- Guide to Java Instrumentation (lacking lots of details)
- Repo of samples "core-java-jvm" on GitHub (lacking lots of details)
- Maven clean and install
- Example of using maven to package an agent
- Example of using IDEA and maven to build an agent
Basically, the API allows adding code before or after already existing code (w/out modifying any of the existing code itself). Or you can simply swap out entire classes. Here's a short list of things you can do with this API:
addTransformer
– adds a transformer to the instrumentation enginegetAllLoadedClasses
– returns an array of all classes currently loaded by the JVMretransformClasses
– facilitates the instrumentation of already loaded classes by adding byte-coderemoveTransformer
– unregisters the supplied transformerredefineClasses
– redefine the supplied set of classes using the supplied class files, meaning that the class will be fully replaced, not modified as withretransformClasses
Sample project that shows how to use this (and agents from the section below): repo.
In general, a java agent is just a specially crafted jar file. It utilizes the Instrumentation API that the JVM provides to alter existing byte-code that is loaded in a JVM.
For an agent to work, we need to define two methods:
premain
– will statically load the agent using-javaagent
parameter at JVM startup. Static load modifies the byte-code at startup time before any code is executed.agentmain
– will dynamically load the agent into an already running JVM using the Java Attach API
Basics, IDE specific guides, API references:
Good tutorials & slides (these give an idea of what can be done w/ agents):
- Tutorial: Guide to Java instrumentation
- Slide deck on Java agents
- Tutorial: Quick intro to Java agents
- Tutorial: Quick intro to Java agents
- Tutorial: Quick intro to Java agents
- Tutorial: Quick intro to Java agents
Interesting libraries related to agents (good to look at source):