Understanding JVMTI and Java Agents: From Startup to Runtime Instrumentation
This article explains the principles behind JVM monitoring tools by introducing JVMTI, the Java Instrumentation API, startup and runtime agents, the Attach mechanism, and demonstrates bytecode manipulation with ASM through detailed code examples and diagrams.
In everyday development we inevitably use debugging tools, and the JVM, as a separate process, can be inspected via tools like JMAP, JSTACK, JEX, Arthas, or SkyWalking. This article explains the technical mechanisms these tools use to monitor and dynamically modify the JVM.
From JVMTI
The JVM was designed with monitoring, debugging, thread, and memory analysis in mind. Before JDK 5, the specifications defined JVMPI (Profiler Interface) and JVMDI (Debug Interface). Starting with JDK 5, these were merged into JVMTI (Java Virtual Machine Tool Interface), a native API accessed via JNI and implemented in C/C++ as a dynamic library loaded by the JVM.
Typical usage involves loading JVMTI through JNI, as illustrated in the diagram showing the JNI call flow.
Instrument Agent
Since JDK 1.5, the Java java.lang.instrument package allows developers to write agents in Java, but the underlying implementation still relies on JVMTI. The agent JAR must declare a Premain-Class in its MANIFEST.MF and provide a premain method (either premain(String, Instrumentation) or premain(String) ). The article provides a sample agent class AgentMain that registers a ClassFileTransformer to intercept class loading.
import java.lang.instrument.Instrumentation;
public class AgentMain {
// JVM startup agent
public static void premain(String args, Instrumentation inst) {
agent0(args, inst);
}
public static void agent0(String args, Instrumentation inst) {
System.out.println("agent is running!");
// Add a class transformer
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class
classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
// JVM loads all classes through this transformer
if (className.endsWith("WorkerMain")) {
System.out.println("transform class WorkerMain");
}
// Return original bytecode
return classfileBuffer;
}
});
}
}The manifest must contain:
Premain-Class: AgentMain
Can-Redefine-Classes: true
Can-Retransform-Classes: trueRunning the JVM with -javaagent:xxx.jar loads the agent at startup, as shown by the execution screenshot.
Runtime Agent
From JDK 1.6 onward, the JVM supports runtime agents via the Agent-Class manifest entry and an agentmain method. The runtime agent uses the Attach mechanism, which communicates over a socket listener inside the JVM. The article provides an AttachUtil class that lists running JVMs, attaches to the target, and loads the agent JAR.
public class AttachUtil {
public static void main(String[] args) throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException {
// Get list of running JVMs
List
vmList = VirtualMachine.list();
String agentJar = "xxxx/agent-test.jar";
for (VirtualMachineDescriptor vmd : vmList) {
if (vmd.displayName().endsWith("WorkerMain")) {
VirtualMachine vm = VirtualMachine.attach(vmd.id());
vm.loadAgent(agentJar);
vm.detach();
}
}
}
}The combined agent class now implements both premain and agentmain , and uses inst.retransformClasses to trigger class redefinition at runtime.
public class AgentMain {
// JVM startup agent
public static void premain(String args, Instrumentation inst) { agent0(args, inst); }
// JVM runtime agent
public static void agentmain(String args, Instrumentation inst) { agent0(args, inst); }
public static void agent0(String args, Instrumentation inst) {
System.out.println("agent is running!");
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class
classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
System.out.println(className);
return classfileBuffer;
}
}, true);
try {
Class
c = Class.forName("test.WorkerMain");
inst.retransformClasses(c);
} catch (Exception e) {
System.out.println("error!");
}
}
}To actually modify bytecode, the article introduces ASM. A ClassFileTransformer implementation called MyClassTransformer uses ASM's ClassReader , ClassWriter , and a custom MyEnhancer (extending ClassVisitor ) to weave timing and argument‑logging code into methods.
public class MyClassTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class
classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
ClassReader reader = new ClassReader(classfileBuffer);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
MyEnhancer enhancer = new MyEnhancer(writer);
reader.accept(enhancer, ClassReader.SKIP_FRAMES);
return writer.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}The enhancer inserts calls to a helper class MethodContainer.showMethod(long, Object[]) at method entry and exit, capturing execution time and arguments.
public class MethodContainer {
public static void showMethod(long startTime, Object[] args) {
System.out.println("Method cost:" + (System.nanoTime() - startTime) / 1000000 + "ms, args:" + Arrays.toString(args));
}
}After deploying the transformer, running AttachUtil redefines the target class in a live JVM, demonstrating seamless runtime instrumentation without restarting the application.
Summary
For Java developers, code instrumentation is a familiar concept, with many mature solutions such as Spring AOP (dynamic proxies) and Lombok (annotation processors). While those approaches suffice for logging and simple monitoring, JVM‑level instrumentation via Java agents offers unparalleled flexibility for performance analysis and real‑time method tracing.
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.