Mastering Java Agents: Premain vs Attach for Bytecode Manipulation
This article explains how Java agents work, compares premain and attach agents, shows how to implement entry points, use Instrumentation and Javassist to transform bytecode, provides complete code samples, packaging instructions, and demonstrates the runtime output with diagrams.
What is a Java Agent?
Java agents are a mechanism that allows code to be injected into a JVM at runtime. They are widely used by tools such as Arthas and tracing frameworks to modify class bytecode.
Two Types of Java Agents
Premain agent : loaded before the JVM starts, specified with the -javaagent option.
Attach agent : loaded into an already‑running JVM via the Attach API.
Differences
Premain agents can transform classes before their first load because they run before the JVM starts.
Attach agents operate after classes have been loaded; they must trigger a re‑load (retransform) of the target classes to apply changes.
Premain Agent
Define the entry point in the JAR manifest with Premain-Class: org.example.PreMainAgent. The JVM calls one of the following methods when the agent is loaded:
public static void premain(String agentArgs, Instrumentation inst) public static void premain(String agentArgs)A minimal premain agent:
package org.example;
import java.lang.instrument.Instrumentation;
public class PreMainAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("PreMainAgent premain enter.args=" + agentArgs);
}
}To transform classes, register a ClassFileTransformer via inst.addTransformer(...). The transformer receives the original bytecode and may return a modified byte[].
static class DemoTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
System.out.println("PreMainAgent start transform. className=" + className);
return null; // no modification
}
}Practical example that inserts a print statement after sayHello in org.example.simple.Hello using Javassist:
if ("org/example/simple/Hello".equals(className)) {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("org.example.simple.Hello");
CtMethod m = cc.getDeclaredMethod("sayHello");
m.insertAfter("System.out.println(\"hello world!\");");
byte[] byteCode = cc.toBytecode();
cc.detach();
return byteCode;
}
return null;Complete premain agent that registers the transformer:
package org.example;
import java.lang.instrument.*;
import java.security.ProtectionDomain;
import javassist.*;
public class PreMainAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("PreMainAgent premain enter.args=" + agentArgs);
inst.addTransformer(new DemoTransformer(), true);
}
static class DemoTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
System.out.println("PreMainAgent start transform. className=" + className);
if ("org/example/simple/Hello".equals(className)) {
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("org.example.simple.Hello");
CtMethod m = cc.getDeclaredMethod("sayHello");
m.insertAfter("System.out.println(\"hello world!\");");
byte[] byteCode = cc.toBytecode();
cc.detach();
return byteCode;
} catch (Throwable t) {
t.printStackTrace();
}
}
return null;
}
}
}Test application that repeatedly calls Hello.sayHello():
package org.example;
import org.example.simple.Hello;
public class App {
public static void main(String[] args) throws InterruptedException {
System.out.println("entry main.");
while (true) {
Hello.sayHello();
Thread.sleep(1000);
}
}
} package org.example.simple;
public class Hello {
public static void sayHello() {
System.out.println("hello");
}
}Run with:
java -javaagent:AgentDemo-1.0-SNAPSHOT-jar-with-dependencies.jar org.example.AppSample output:
PreMainAgent premain enter.args=null
PreMainAgent start transform. className=org/example/App
entry main.
PreMainAgent start transform. className=org/example/simple/Hello
hello
hello world!
hello
hello world!
...Premain flow diagram:
Attach Agent
Define the entry point in the JAR manifest with Agent-Class: org.example.AttachAgent. The JVM calls one of the following methods when the agent is attached:
public static void agentmain(String agentArgs, Instrumentation inst) public static void agentmain(String agentArgs)Minimal attach agent:
package org.example;
import java.lang.instrument.Instrumentation;
public class AttachAgent {
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("entry AttachAgent main.");
}
}Helper program that uses the Attach API to list running JVMs and load the agent:
package org.example;
import com.sun.tools.attach.*;
import java.util.*;
public class AttachAgentMain {
public static void main(String[] args) {
System.out.println("Attach test agent start.");
List<VirtualMachineDescriptor> vms = VirtualMachine.list();
for (int i = 0; i < vms.size(); i++) {
System.out.println(i + " : " + vms.get(i).displayName());
}
try (Scanner scanner = new Scanner(System.in)) {
int sel = scanner.nextInt();
VirtualMachineDescriptor vmd = vms.get(sel);
VirtualMachine vm = VirtualMachine.attach(vmd.id());
vm.loadAgent("/path/to/AgentDemo-1.0-SNAPSHOT-jar-with-dependencies.jar");
vm.detach();
} catch (Throwable e) {
e.printStackTrace();
}
}
}Attach agent registers a transformer and then re‑transforms the target class:
public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
DemoTransformer demo = new DemoTransformer();
inst.addTransformer(demo, true);
Class<?> target = Class.forName("org.example.simple.Hello");
inst.retransformClasses(target);
}
static class DemoTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
System.out.println("AttachAgent start transform. className=" + className);
if ("org/example/simple/Hello".equals(className)) {
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("org.example.simple.Hello");
CtMethod m = cc.getDeclaredMethod("sayHello");
m.insertAfter("System.out.println(\"hello world after attach agent transform!\");");
byte[] byteCode = cc.toBytecode();
cc.detach();
return byteCode;
} catch (Throwable t) {
t.printStackTrace();
}
}
return null;
}
}Attach flow diagram:
Packaging the Agent
The agent JAR must contain all dependencies to avoid java.lang.NoClassDefFoundError. Use Maven’s maven-assembly-plugin with the jar-with-dependencies descriptor and add the required manifest entries.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-5</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>org.example.PreMainAgent</Premain-Class>
<Agent-Class>org.example.AttachAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<goals>
<goal>assembly</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>References
https://www.cnblogs.com/rickiyang/p/11368932.html https://lotabout.me/2024/Java-Agent-101/ https://www.cnblogs.com/qisi/p/java_agent.html https://mp.weixin.qq.com/s?__biz=MzAwNjQwNzU2NQ==&mid=2650403102&idx=1&sn=659b9a08c764665a60b1e3baad5df531Signed-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.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
