Java agents and their instrumentation API offer developers the most powerful toolset to interact with a Java application. Using this API, it becomes possible to alter the code of running applications, for example to add monitoring or to inject security checks as it is done by many enterprise products for the Java ecosystem.
In this session, developers will learn how to program Java agents of their own that make use of the instrumentation API. Doing so, developers learn how the majority of tooling for the JVM is implemented and will learn about Byte Buddy, a high level code generation library that does not require any knowledge of Java byte code that is normally required for writing agents. In the process, developers will see how Java classes can be used as templates for implementing highly performant code changes that avoid the boilerplate of alternative solutions such as AspectJ or Javassist while still performing better than agents implemented in low-level libraries such as ASM.
5. java -cp:. main.AppEntry
Running a dynamic Java agent
class AgentEntry {
public static void agentmain(String arg) {
System.out.println("Hello from " + arg);
}
}
VirtualMachine vm = VirtualMachine.attach(pid);
try {
vm.loadAgent("/home/user/myagent.jar", "MyAgent");
} finally {
vm.detach();
}
PID a
PID b
Ifownedbysameuser
6. java -cp:. -javaagent:/home/user/myagent.jar=MyAgent main.AppEntry
Transforming a class
class PreEntry {
public static void premain(String arg, Instrumentation inst) {
inst.addTransformer(
(classLoader, className, classIfLoaded, protectionDomain, classFile) -> {
if (shouldTransform(className)) {
return transform(classFile);
} else {
return null;
}
});
}
}
7. java -cp:. main.AppEntry
Retransforming a class
class AgentEntry {
public static void agentmain(String arg, Instrumentation inst) {
inst.addTransformer(
(classLoader, className, classIfLoaded, protectionDomain, classFile) -> {
if (shouldTransform(classIfLoaded)) {
return transform(classFile);
} else {
return null;
}
}, true);
inst.retransformClasses(getClassesToRetransform());
}
}
8. Retransforming a class: limitations
class Sample {
String m() {
return "foo";
}
}
class Sample {
String m() {
return "bar";
}
}
class Sample {
String m() {
return m2();
}
private static String m2() {
return "bar";
}
}
class Sample {
String m() {
return m2();
}
private String m2() {
return "bar";
}
}
Possible future extension: JEP 159 or Dynamic code evolution VM
But currently this does not seem to be a goal.
50. long pid = ProcessHandle.current().pid();
VMHelper.runInNewVm(() -> {
VirtualMachine vm = VirtualMachine.attach(String.valueOf(pid));
try {
vm.loadAgent(MyAttachHelper.class.getProtectionDomain()
.getCodeSource()
.getURL()
.toString(), null);
} finally {
vm.detach();
}
});
Instrumentation inst = MyAttachHelper.inst;
Self-attaching for testing a Java agent
long pid = ProcessHandle.current().pid();
VirtualMachine vm = VirtualMachine.attach(String.valueOf(pid));
try {
vm.loadAgent(MyAttachHelper.class.getProtectionDomain()
.getCodeSource()
.getURL()
.toString(), null);
} finally {
vm.detach();
}
class MyAttachHelper {
Instrumentation inst;
public static void agentmain(String arg, Instrumentation inst) {
MyAttachHelper.inst = inst;
}
}
51. Attachment using Byte Buddy
Instrumentation inst = ByteBuddyAgent.install();
interface AttachmentProvider
1. jdk.attach (JDK only) / IBM-namespace-aware
2. jdk.attach via helper process (JDK only) / IBM-namespace-aware
3. Attachment emulation (via JNA/JNI), no self-attachment constraint (POSIX, Solaris, Windows / HotSpot, OpenJ9)
ByteBuddyAgent.attach(new File("myagent.jar"), "123");
52. Instance-bound state without adding fields
public class StateHandler { // injected into boot loader
public static final Map<Object, Object> state =
Collections.synchronizedMap(new WeakHashMap<>());
}
public class StateHandler { // injected into boot loader
public static final WeakConcurrentMap<Object, Object> state =
new WeakConcurrentMap.WithInlinedExpunction<>();
}
class UserClass {
void foo() {
// ...
}
}
class UserClass {
static Dispatcher dispatcher;
static {
Class.forName(
"net.bytebuddy.Nexus", false, ClassLoader.getSystemClassLoader())
.getMethod("init", Class.class)
.invoke(null, UserClass.class);
}
void foo() {
dispatcher.run();
// ...
}
}
53. class AgentEntry {
public static void agentmain(String arg, Instrumentation inst) {
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(RedefinitionStrategy.RETRANSFORM)
.with(BatchAllocator.Partitioning.of(4))
.with(DiscoveryStrategy.Reiterating.INSTANCE)
.type(aLotOfTypes())
.transform((builder, type, classLoader, module) -> builder
.visit(Advice.to(SomeAdvice.class).on(isMethod()))
.installOn(inst);
}
}
class AgentEntry {
public static void agentmain(String arg, Instrumentation inst) {
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(RedefinitionStrategy.RETRANSFORM)
.with(BatchAllocator.Partitioning.of(4))
.with(DiscoveryStrategy.Reiterating.INSTANCE)
.with(new ResubmissionScheduler.WithFixedDelay(
Executors.newSingleThreadScheduledExecutor(), 1, TimeUnit.SECOND))
.type(aLotOfTypes())
.transform((builder, type, classLoader, module) -> builder
.visit(Advice.to(SomeAdvice.class).on(isMethod()))
.installOn(inst);
}
}
class AgentEntry {
public static void agentmain(String arg, Instrumentation inst) {
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(RedefinitionStrategy.RETRANSFORM)
.type(aLotOfTypes())
.transform((builder, type, classLoader, module) -> builder
.visit(Advice.to(SomeAdvice.class).on(isMethod()))
.installOn(inst);
}
}
Increasing retransformation resilience
class AgentEntry {
public static void agentmain(String arg, Instrumentation inst) {
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(RedefinitionStrategy.RETRANSFORM)
.with(BatchAllocator.Partitioning.of(4))
.type(aLotOfTypes())
.transform((builder, type, classLoader, module) -> builder
.visit(Advice.to(SomeAdvice.class).on(isMethod()))
.installOn(inst);
}
}
54. Balancing performance: JIT and GC inference
-XX:MaxInlineLevel // maximum nested calls
-XX:MaxInlineSize // maximum byte code size
-XX:FreqInlineSize // maximum byte code size (frequently called)
-XX:MaxTrivialSize // maximum byte code size (trivial, e.g. getter/setter)
-XX:MinInliningThreshold // minimum amount of calls before inlining
-XX:LiveNodeCountInliningCutoff // max number of live nodes
object life time
main
agent
object life time: young generation, tenured generation