Unlock JVM Power: Master Java Agent Premain & Agentmain for Runtime Instrumentation

This tutorial explains Java Agent technology, compares it with AOP, and provides step‑by‑step implementations of Premain and Agentmain modes, covering agent packaging, manifest configuration, instrumentation APIs, class transformation, redefinition, retransformation, and integration with Javassist for dynamic bytecode manipulation.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Unlock JVM Power: Master Java Agent Premain & Agentmain for Runtime Instrumentation

Developers familiar with Spring AOP will recognize the concept of weaving logic before and after target methods; Java Agent works similarly at the JVM level, allowing monitoring, runtime modification, or replacement of programs.

Java Agent usage scenarios
Java Agent usage scenarios

The article compares Java Agent with AOP, highlighting that AOP operates at the method level inside applications, while agents operate at the virtual machine level and require two projects: the agent and the main program.

Scope: AOP works at method level, agents at JVM level.

Components: AOP needs target method and enhancement logic; agents need an agent project and a main program.

Execution: AOP can run before, after, or around a method; agents have two execution modes— premain (executed before the main program) and agentmain (executed after the main program).

Premain mode

The Premain mode runs an agent before the main program starts. Two parts are required: the agent and the main program.

Agent

A simple agent prints a message and the arguments passed to it.

public class MyPreMainAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("premain start");
        System.out.println("args:" + agentArgs);
    }
}

The agent must be packaged into a JAR with a manifest that specifies Premain-Class and optional attributes such as Can-Redefine-Classes, Can-Retransform-Classes, and Can-Set-Native-Method-Prefix.

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Premain-Class>com.cn.agent.MyPreMainAgent</Premain-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

After building, the JAR (e.g., myAgent-1.0.jar) can be attached to a main program using the -javaagent VM option.

Main program

The main program only needs a main method.

public class AgentTest {
    public static void main(String[] args) {
        System.out.println("main project start");
    }
}

Run with:

java -javaagent:myAgent.jar -jar AgentTest.jar

Multiple agents can be chained by specifying several -javaagent options.

Agent execution order
Agent execution order

Limitations

If an agent throws an exception during Premain execution, the main program fails to start. This drawback led to the introduction of the Agentmain mode in JDK 1.6.

Agentmain mode

Agentmain allows the target JVM to start first, then attaches the agent via the attach mechanism.

Agent

public class MyAgentMain {
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("agent main start");
        System.out.println("args:" + agentArgs);
    }
}

The JAR manifest must contain Agent-Class in addition to the optional attributes.

Main program

public class AgentmainTest {
    public static void main(String[] args) throws IOException {
        System.in.read(); // block to keep JVM alive
    }
}

Attach the agent from another process using com.sun.tools.attach.VirtualMachine:

VirtualMachine vm = VirtualMachine.attach("16392");
vm.loadAgent("F:\\Workspace\\MyAgent\\target\\myAgent-1.0.jar", "param");
Agentmain attachment
Agentmain attachment

Instrumentation API

The Instrumentation interface provides powerful runtime capabilities such as adding transformers, redefining classes, retransformation, and accessing loaded classes.

addTransformer

Registers a ClassFileTransformer that can modify bytecode before a class is defined.

public class FruitTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        if (!className.equals("com/cn/hydra/test/Fruit")) {
            return classfileBuffer;
        }
        String fileName = "F:\\Workspace\\agent-test\\target\\classes\\com\\cn\\hydra\\test\\Fruit2.class";
        return getClassBytes(fileName);
    }
    // getClassBytes reads the replacement .class file
}

Using inst.addTransformer(new FruitTransformer()) together with -javaagent replaces the original Fruit class, changing its output from apple to banana.

redefineClasses

Directly replaces the bytecode of already loaded classes.

public class RedefineAgent {
    public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
        String fileName = "F:\\Workspace\\agent-test\\target\\classes\\com\\cn\\hydra\\test\\Fruit2.class";
        ClassDefinition def = new ClassDefinition(Fruit.class, FruitTransformer.getClassBytes(fileName));
        inst.redefineClasses(def);
    }
}

retransformClasses

Works in Agentmain mode to re‑transform already loaded classes.

public class RetransformAgent {
    public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
        inst.addTransformer(new FruitTransformer(), true);
        inst.retransformClasses(Fruit.class);
        System.out.println("retransform success");
    }
}

After attachment, the Fruit method output changes at runtime.

Javassist integration

Javassist simplifies bytecode editing. The following agent measures method execution time.

public class Agent {
    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new LogTransformer());
    }
    static class LogTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            if (!className.equals("com/cn/hydra/test/Fruit")) return null;
            try { return calculate(); } catch (Exception e) { e.printStackTrace(); return null; }
        }
        static byte[] calculate() throws Exception {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.get("com.cn.hydra.test.Fruit");
            CtMethod ctMethod = ctClass.getDeclaredMethod("getFruit");
            CtMethod copyMethod = CtNewMethod.copy(ctMethod, ctClass, new ClassMap());
            ctMethod.setName("getFruit$agent");
            String body = "{ long begin = System.nanoTime(); getFruit$agent($$); " +
                          "System.out.println(\"use \"+(System.nanoTime()-begin)+\" ns\"); }";
            copyMethod.setBody(body);
            ctClass.addMethod(copyMethod);
            return ctClass.toBytecode();
        }
    }
}

Running the main program now prints the original fruit name followed by the execution time, demonstrating dynamic instrumentation.

Conclusion

Java Agent, though not frequently used directly, underpins many hot‑deployment, monitoring, and performance‑analysis tools. By mastering Premain and Agentmain modes and the Instrumentation API, developers gain powerful capabilities to modify running JVMs, opening endless possibilities for advanced Java applications.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

InstrumentationJava AgentBytecode ManipulationPremainAgentmain
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.