The purpose of this agent is to fix excessive object allocation issues. While most profilers do offer allocation tracking, they all struggle heavily when the garbage issue is really out of hand. Examples are for example gigabytes of object allocations per second.
This single purpose agent is extremely lightweight and highly optimized using a Calling Context Tree (CCT) with thread-local buffers (improvement PRs are welcome at any time!).
It is intended to be inserted into production or load test environments and will give you a report showing not just allocation counts, but complete stack traces showing WHERE objects are being allocated.
The agent captures the calling context (stack trace) for each allocation, allowing you to identify the exact code paths responsible for excessive allocations.
The agent does not differentiate between live and garbage objects, issues with live objects can usually be solved with a heap dump, finding out which objects create garbage is usually much harder.
By design you have to provide a comma-separated list of specific class names to instrument. The agent now supports instrumenting JDK classes by automatically adding the agent JAR to the bootstrap class loader and enabling retransformation capabilities.
If you create more than Long.MAX_VALUE instances of a tracked class the counter will roll over without you noticing it, but that scenario is unlikely. The agent uses LongAdder for high-performance counter updates under contention (standard in Java 17+ build).
The agent is controlled by a PlatformMBeanServer MBean. This might require activation on certain application servers.
For performance reasons the agent does not try to figure out if a class is a superclass or not. Thus superclasses will be included in the report and common base classes will likely appear at the top.
The agent is built as a multi-release JAR supporting Java 17+ with optimizations for Java 21+.
Build Requirements:
- JDK 21 (for building)
- Maven 3.6.3+
Build Command:
mvn clean packageThe resulting JAR will automatically include:
- Base version for Java 17+
- Optimized version for Java 21+
The JVM will automatically select the appropriate version at runtime based on the Java version being used.
Minimum Java version: Java 17
Run your application with -javaagent:/path/to/agent.jar=class1,class2,class3
Examples:
# Instrument application classes
java -javaagent:/path/to/allocation-tracker-agent-0.0.1-SNAPSHOT.jar=de.codecentric.MyClass,com.example.OtherClass \
-jar your-application.jar
# Instrument JDK classes (supported)
java -javaagent:/path/to/allocation-tracker-agent-0.0.1-SNAPSHOT.jar=java.lang.String,java.util.ArrayList \
-jar your-application.jarAfter the agent has printed codecentric allocation agent - Registered Agent MBean. the agent is ready to use.
Connect to the JVM with any JMX client and use the de.codecentric.Agent MBean to start tracking allocations. Use stop to stop tracking allocations (this also flushes thread-local buffers). printTop can be used any time to print the allocation hot paths with full stack traces. The parameter will control the amount of output. If the parameter is zero or less, it will default to 100.
The output shows a clean hierarchical tree where parent nodes aggregate counts from all child paths:
Allocation Hot Paths (merged view):
===================================
2000 - java.nio.DirectByteBuffer.<init>
├── 1340 - java.nio.DirectByteBuffer.duplicate
│ ├── 893 - com.sun.crypto.provider.GaloisCounterMode$GCMDecrypt.doFinal
│ │ └── 893 - com.sun.crypto.provider.GaloisCounterMode.engineDoFinal
│ │ └── 893 - javax.crypto.Cipher.doFinal
│ └── 447 - sun.security.ssl.SSLCipher$T13GcmReadCipherGenerator$GcmReadCipher.decrypt
│ └── 447 - sun.security.ssl.SSLEngineInputRecord.decodeInputRecord
├── 456 - java.nio.DirectByteBuffer.slice
│ └── 451 - sun.security.ssl.SSLCipher$T13GcmReadCipherGenerator$GcmReadCipher.decrypt
└── 204 - io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized
The tree structure makes it easy to see allocation hot paths at a glance. The numbers represent total allocations through that call path (including all descendants).
Java 21+ users automatically benefit from additional optimizations including enhanced collection APIs and improved performance characteristics.
Glad that you asked. Please take a look at the source files and then come up with a sensible unit test. If you manage to do that feel free to put up a PR.