Mastering Java Instrumentation: Non‑Intrusive Method Timing with Agents and Arthas
This article explains how to replace invasive manual timing code with Java Instrumentation, demonstrating both premain and agentmain approaches, building and attaching agents, using ASM and Bytekit for bytecode enhancement, and leveraging Arthas for runtime tracing and debugging.
Problem Statement
Team members often insert repetitive timing code to measure method execution time, which is intrusive and hard to maintain.
Java Instrumentation Overview
Since JDK 1.5, the
java.lang.instrumentpackage provides tools for bytecode manipulation. The
Instrumentationinterface offers methods such as
addTransformer,
removeTransformer,
retransformClasses, and
getAllLoadedClasses.
<code>public interface Instrumentation {
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
boolean removeTransformer(ClassFileTransformer transformer);
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
boolean isRetransformClassesSupported();
Class[] getAllLoadedClasses();
}
</code>Using Java Agent (premain)
The
premainmethod runs before
main(). By registering a
ClassFileTransformerinside
premain, you can modify class bytecode during class loading.
<code>public static void premain(String agentArgs, Instrumentation instrumentation) {
System.out.println("premain method");
instrumentation.addTransformer(new MyClassFileTransformer(), true);
}
</code>Attaching Agent at Runtime (agentmain)
The
agentmainmethod is invoked when an agent is attached to a running JVM via the Attach API. It allows dynamic re‑transformation of already loaded classes.
<code>public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
System.out.println("agentmain");
inst.addTransformer(new PrintNumTransformer(), true);
for (Class loaded : inst.getAllLoadedClasses()) {
if (loaded.getSimpleName().equals("PrintNumTest")) {
System.out.println("Reloading: " + loaded.getName());
inst.retransformClasses(loaded);
break;
}
}
}
</code>Building the Agent JAR
The Maven
assemblyplugin can create a fat JAR that includes all dependencies. The manifest must specify
Premain-Classand
Agent-Classentries.
<code><plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Agent-Class>com.example.aop.agent.AgentMain</Agent-Class>
<Premain-Class>com.example.aop.agent.AgentMain</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</code>Testing the Agent
Run the target application with
-javaagentto trigger
premain, or use a separate program to attach the agent at runtime.
<code>java -javaagent:/path/to/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.example.aop.agent.MyTest
public class MyTest {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(3000);
}
}
</code>After attaching, the printed number changes from
100to
50as shown below.
Arthas Debugging and Tracing
Arthas can attach to a JVM for remote debugging. By setting breakpoints in the source code (e.g.,
com.taobao.arthas.agent334.AgentBootstrap#main) and launching
arthas-boot.jar, you can step into the Arthas source.
Bytekit for Simplified Bytecode Enhancement
Bytekit provides a concise API over ASM. An interceptor class can declare
@AtEnter,
@AtExit, and
@AtExceptionExitmethods to inject code at method entry, exit, or exception.
<code>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) {
System.out.println("atEnter, args[0]: " + args[0]);
}
@AtExit(inline = true)
public static void atExit(@Binding.Return Object ret) {
System.out.println("atExit, returnObject: " + ret);
}
}
</code>Bytekit generates additional bytecode, turning the original method into a larger version that calls the interceptor methods.
<code>public String hello(String str, boolean exception) {
try {
SampleInterceptor.atEnter(this, Sample.class, new Object[]{str, Boolean.valueOf(exception)}, "hello");
if (exception) {
this.exceptionCount++;
throw new RuntimeException("test exception, str: " + str);
}
String result = "hello " + str;
System.out.println("atExit, returnObject: " + result);
return result;
} catch (RuntimeException e) {
System.out.println("atExceptionExit, ex: " + e.getMessage() + ", field exceptionCount: " + this.exceptionCount);
throw e;
}
}
</code>Arthas Trace Command Implementation
The
tracecommand is handled by
TraceCommand, which extends
EnhancerCommand. The core logic resides in
Enhancer.enhance, which registers a
ClassFileTransformer, parses interceptor classes, and modifies matching methods.
<code>public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain pd, byte[] buf) throws IllegalClassFormatException {
// Load class node
ClassNode cn = new ClassNode(Opcodes.ASM9);
ClassReader cr = new ClassReader(buf);
cr.accept(cn, 0);
// Parse interceptors
List<InterceptorProcessor> processors = ...;
for (MethodNode mn : cn.methods) {
if (!isIgnore(mn)) {
MethodProcessor mp = new MethodProcessor(cn, mn);
for (InterceptorProcessor ip : processors) {
ip.process(mp);
}
AdviceListenerManager.registerAdviceListener(loader, className, mn.name, mn.desc, listener);
}
}
return AsmUtils.toBytes(cn, loader, cr);
}
</code>The transformer inserts listeners for method entry/exit and, when tracing, registers
AdviceListenerobjects that collect timing information.
Overall Process Flow
The diagram below summarizes the steps: load interceptor classes, parse annotations, generate
InterceptorProcessorobjects, traverse class methods, apply bytecode modifications, and finally register advice listeners.
References
https://developer.aliyun.com/article/768074
https://arthas.aliyun.com/doc/trace.html#注意事项
https://blog.csdn.net/tianjindong0804/article/details/128423819
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.