Backend Development 20 min read

Java Agent and Instrumentation: Non‑Intrusive Method Timing, Attach API, and Arthas Trace

This article demonstrates how to replace invasive manual timing code with Java Agent‑based instrumentation, covering the use of java.lang.instrument, premain and agentmain methods, dynamic class retransformation via the Attach API, and practical examples including method‑level timing, runtime class modification, and integration with Arthas for tracing.

Architect
Architect
Architect
Java Agent and Instrumentation: Non‑Intrusive Method Timing, Attach API, and Arthas Trace

The article starts by describing a common performance‑optimization scenario where developers sprinkle manual timing code throughout a project, leading to highly intrusive and hard‑to‑maintain implementations.

It then introduces Java Agent technology as a non‑intrusive alternative, explaining that the java.lang.instrument package (available since JDK 1.5) provides the Instrumentation interface for bytecode enhancement.

Key methods of the Instrumentation interface are listed, such as addTransformer , removeTransformer , retransformClasses , and isRetransformClassesSupported , along with the ClassFileTransformer contract for transforming class byte arrays.

Two ways to load an agent are explained:

Adding the agent JAR to the JVM at startup via the premain method.

Attaching the agent to a running JVM using the Attach API and the agentmain method.

Example code shows a simple premain implementation that registers a MyClassFileTransformer to instrument a target method and record its execution time:

@Override
public void method(Req req) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start("某某方法-耗时统计");
    method();
    stopWatch.stop();
    log.info("查询耗时分布:{}", stopWatch.prettyPrint());
}

The transformer uses ASM to insert calls to a TimeStatistics helper class at method entry and exit, producing bytecode similar to:

public class MyClassFileTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class
classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        if ("com/example/aop/agent/MyTest".equals(className)) {
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new TimeStatisticsVisitor(Opcodes.ASM7, cw);
            cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
        return classfileBuffer;
    }
}

For runtime attachment, the article presents a PrintNumTest program that continuously prints 100 . An agent using agentmain and a transformer modifies the getNum method to return 50 instead, demonstrating dynamic class redefinition:

public class PrintNumTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class
classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        if ("com/example/aop/agent/PrintNumTest".equals(className)) {
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new TransformPrintNumVisitor(Opcodes.ASM7, cw);
            cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
        return classfileBuffer;
    }
}

The Attach API is used from a separate Java process to load the agent into the target JVM:

VirtualMachine vm = VirtualMachine.attach(pid);
try {
    vm.loadAgent("/path/to/agent.jar");
} finally {
    vm.detach();
}

After showing the successful output change, the article briefly mentions using Alibaba's Arthas tool for tracing. It explains that Arthas implements its own bytecode enhancement using the same instrumentation mechanisms, with interceptors such as @AtEnter , @AtExit , and @AtExceptionExit (provided by the bytekit library) to inject tracing logic.

Sample bytekit interceptor code is shown:

public class SampleInterceptor {
    @AtEnter(inline = false, suppress = RuntimeException.class, suppressHandler = PrintExceptionSuppressHandler.class)
    public static void atEnter(@Binding.This Object obj, @Binding.Class Class
clazz,
                               @Binding.Args Object[] args, @Binding.MethodName String methodName,
                               @Binding.MethodDesc String methodDesc) {
        System.out.println("atEnter, args[0]: " + args[0]);
    }

    @AtExit(inline = true)
    public static void atExit(@Binding.Return Object returnObject) {
        System.out.println("atExit, returnObject: " + returnObject);
    }
}

Finally, the article lists several reference links for deeper reading on Java Agent, Arthas trace command, and bytecode manipulation.

JavaperformanceInstrumentationBytecodeArthasJavaAgent
Architect
Written by

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.

0 followers
Reader feedback

How this landed with the community

login 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.