How to Use Java Agents for Runtime Bytecode Manipulation with ASM
This article explains the basics of Java agents, demonstrates how to use premain and agentmain to modify bytecode at load time or during execution, and provides practical examples with ASM to monitor method execution time and capture method parameters and return values in running Java processes.
Overview
Java agents provide a way to modify bytecode when it is loaded by the JVM. They can be executed either before the main method via premain or while the application is running via the attach API and agentmain.
Instrumentation API
The JDK 1.5 Instrumentation API allows registration of class file transformers that can rewrite bytecode during class loading.
public interface Instrumentation {
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
// other methods omitted for brevity
}Simple premain Example
A minimal agent that prints a message before the application starts:
package org.xunche.agent;
public class HelloAgent {
public static void premain(String args) {
System.out.println("Hello Agent: " + args);
}
}To package the agent, create a manifest with Premain-Class: org.xunche.agent.HelloAgent and build a JAR:
echo 'Premain-Class: org.xunche.agent.HelloAgent' > manifest.mf
javac org/xunche/agent/HelloAgent.java
jar cvfm hello-agent.jar manifest.mf org/xunche/agent/HelloAgent.classRun the application with the agent:
java -javaagent:hello-agent.jar=xunche org.xunche.app.HelloWorldThe output shows the agent message followed by the application output.
Monitoring Method Execution Time with ASM
By using ASM, the agent can insert timing code at method entry and exit. The TimeHolder class stores start timestamps and calculates elapsed time.
public class TimeHolder {
private static Map<String, Long> timeCache = new HashMap<>();
public static void start(String method) { timeCache.put(method, System.currentTimeMillis()); }
public static long cost(String method) { return System.currentTimeMillis() - timeCache.get(method); }
}The transformer registers a ClassVisitor that wraps each method with an AdviceAdapter to call TimeHolder.start and TimeHolder.cost.
agentmain for Running Processes
The agentmain method enables attaching to an already running JVM. The example shows a tool that captures method parameters and return values and sends them over a socket.
public static void agentmain(String args, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
if (args == null) return;
int index = args.lastIndexOf('.');
String className = args.substring(0, index);
String methodName = args.substring(index + 1);
inst.addTransformer(new TraceClassFileTransformer(className.replace('.', '/'), methodName), true);
inst.retransformClasses(Class.forName(className));
}The TraceAdviceAdapter collects method arguments and the return value, then calls Sender.send to transmit a Message object to a server listening on port 9876.
public class Sender {
public static void send(Object response, Object[] request, String className, String methodName) {
Message msg = new Message(response, request, className, methodName);
try (Socket socket = new Socket("localhost", 9876)) {
socket.getOutputStream().write(msg.toString().getBytes());
} catch (IOException e) { e.printStackTrace(); }
}
// Message class omitted for brevity
}Server Side
The server starts a thread that listens on the same port, reads incoming messages, and prints them:
ServerSocket ss = new ServerSocket(9876);
while (true) {
Socket s = ss.accept();
BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream()));
System.out.println("receive message:" + r.readLine());
}Running the client application and attaching the agent results in messages like:
receive message:Message{response=hi, xunche, 1581599464436, request=[xunche], className='org.xunche.app.HelloTraceAgent', methodName='sayHi'}Conclusion
The article introduced the basic usage of Java agents, covering both premain and agentmain, and demonstrated a simple runtime method‑call tracing tool. While the example is minimal, it illustrates the powerful possibilities of bytecode instrumentation used in tools such as BTrace, ARMS, and Spring‑Loaded.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
