Instrumenting JVM Thread Creation with Java Agents
Setting Up the Instrumentation Agent
To capture thread creation events at the JVM level, implement a premain method that registers a class transformer during the agent initializatoin phase.
import java.lang.instrument.Instrumentation;
public class ThreadAuditAgent {
public static void premain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new ThreadSpawnInterceptor());
}
}
Intercepting Thread Class Loading
The transformer intercepts class bytes as they load into the JVM. Target the java.lang.Thread class to instrument its constructors or the start() method.
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import javassist.*;
public class ThreadSpawnInterceptor implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
if (!"java/lang/Thread".equals(className)) {
return classfileBuffer;
}
try {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get("java.lang.Thread");
CtConstructor[] constructors = clazz.getDeclaredConstructors();
for (CtConstructor constructor : constructors) {
constructor.insertAfter(
"System.out.println(\"[AUDIT] Thread created: \" + this.getName());"
);
}
return clazz.toBytecode();
} catch (Exception e) {
e.printStackTrace();
return classfileBuffer;
}
}
}
Sample Application with Thread Pool
Demonstrate the monitoring with a concurrent application spawning multiple worker threads.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentTaskDriver {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Executing task " + taskId +
" in " + Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
}
}
Agent Lifecycle Management
Attach a shutdown hook to ensure the instrumentation agent releases resources when the JVM terminates.
public class ThreadAuditAgent {
private static volatile Instrumentation globalInstrumentation;
public static void premain(String args, Instrumentation inst) {
globalInstrumentation = inst;
inst.addTransformer(new ThreadSpawnInterceptor());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("[AUDIT] Terminating thread monitoring agent");
// Perform cleanup: close file handles, flush buffers
}));
}
public static Instrumentation getInstrumentation() {
return globalInstrumentation;
}
}
Configure the agent manifest in META-INF/MANIFEST.MF:
Premain-Class: ThreadAuditAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Launch with the agent attached:
java -javaagent:thread-audit.jar -jar target-application.jar