<div dir="ltr"><div>I wanted to revive the discussion of JDK-8200559 (<a href="https://bugs.openjdk.org/browse/JDK-8200559">https://bugs.openjdk.org/browse/JDK-8200559</a>) which was continued on this GitHub ticket (<a href="https://github.com/openjdk/jdk/pull/3546">https://github.com/openjdk/jdk/pull/3546</a>).</div><div><br></div><div>As outlined before, Java agents are typically about more than code instrumentation but need to define additional classes. There are two reasons for this:</div><div><br></div><div>1. Agents sometimes need to define helper classes. For example, if an instrumented method returns an interface that is a callback. If the instrumentation wants to replace this callback, a new class needs to be defined first.</div><div>2. Agents often need to define infrastructure for communicating with the agent from instrumented code. For example to communicate back metrics, tracing data or anything else that the agent is supposed to be adding. Often, this is done by injecting a class into a class loader that can be reached from instrumented code, which carries a static field that can carry the "agent state" in the form of a callback object.</div><div><br></div><div>Previously, there has been a proposal for adding an overload with an optional argument to ClassFileTransformer that allows to inject classes into the package and class loader of the currently instrumented class. This works mostly well for (1) but not so well for (2). I have however thought about (2) some more since and wanted to suggest an alternative solution that would make the original suggestion to solve this issue work.<br></div><div><br></div><div>The idea would be to also add a method to LambdaMetafactory. The method would allow registering a custom metafactory dispatcher, which can carry an agent's state and then be utilized to communicate back to the agent. This would render the infrastructure question (2) obsolete. As a more concrete example, this would code out the additional code:<br></div><div><br></div><div>public class LambdaMetafactory {<br><br>    private static final ConcurrentMap<String, ExternalMetafactory> DISPATCHERS = new ConcurrentHashMap<>();<br><br>    public static void register(String id, ExternalMetafactory metafactory) {<br>        Objects.requireNonNull(id, "id");<br>        Objects.requireNonNull(metafactory, "metafactory");<br>        ExternalMetafactory previous = DISPATCHERS.putIfAbsent(id, metafactory);<br>        if (previous == null) {<br>            throw new IllegalArgumentException("External metafactory already registered with id: " + id);<br>        }<br>    }</div><div><br></div><div>    public static boolean remove(String id, ExternalMetafactory metafactory) {</div><div>      return DISPATCHERS.remove(id, metafatory);<br></div><div>    }<br></div><div><br>    public static CallSite externalMetafactory(MethodHandles.Lookup caller,<br>                                               String interfaceMethodName,<br>                                               MethodType factoryType,<br>                                               String id,<br>                                               Object... args) throws LambdaConversionException {<br>        ExternalMetafactory dispatcher = DISPATCHERS.get(id);<br>        if (dispatcher == null) {<br>            throw new LambdaConversionException("No external metafactory registered with id: " + id);<br>        }<br>        return dispatcher.externalConstant(caller, interfaceMethodName, factoryType, args);<br>    }<br><br>    @FunctionalInterface<br>    public interface ExternalMetafactory {<br><br>        CallSite externalConstant(MethodHandles.Lookup caller,<br>                                  String interfaceMethodName,<br>                                  MethodType factoryType,<br>                                  Object... args) throws LambdaConversionException;<br>    }<br>}</div><div><br></div><div>Before instrumenting any class, the agent would register a dispatcher, for example by using a UUID as a key. Then a ClassFileTransformer would apply instrumentations where the key is added to the class file within the invokedynamic call site. The instrumented class can then find "its" metafactory and install a call site that, for example, is bound to an instance that carries the agent's state. </div><div><br></div><div>To some extent this metafactory would also avoid  (1). The invokedynamic bootstrap can now create a new class loader that is a child of the instrumented class's class loader which contains the auxiliary classes directly. Often, the created class loader would also search for classes on the agent's class loader, which results in a very natural programming experience as instrumentations can access both the agent's state and the instrumented class's (or framework's) types.</div><div><br></div><div>I am already offering convenience for this in most of Byte Buddy's API, and it is increasingly better adopted. I also find the resulting agents to be much simpler to maintain and test. For an open source example, I can point to the ElasticSearch APM agent: <a href="https://github.com/elastic/apm-agent-java">https://github.com/elastic/apm-agent-java</a></div><div><br></div><div>The reason this should be in the JDK is that a custom metafactory class needs to be injected into the boot loader, the only globally visible loader, and java.base, as the bootstrap callsite needs to be universally visible. Using the unnamed module and opening all modules to that module does not feel like a good solution. And there is a long row of strange class loaders around that only check for java and javax classes when it comes to delegating down to the boot loader. So in practice it has shown that a custom package on the boot loader works poorly, unfortunately.<br></div><div><br></div><div>The only limitation of that  approach is creating auxiliary classes that subclass or implement package-private classes or interfaces. But this would be covered by the original solution that was suggested to solve (1), as the subclass does not make sense outside of the package anyways.<br></div><div><br></div><div>Summarizing: with a facility like the one above in the java.base module and a solution as originally suggested by Mandy, I do not see a scenario that cannot be covered and I think agents could supply everything they do today also without using Unsafe API. </div><div><br></div><div>This would allow for instrumenting all class files compiled to Java 7 or higher, which is most class files minus some  JDBC drivers.Often, these class files can be upgraded dynamically. For older JDKs, the call site could be emulated by injecting a class using Unsafe. But for the JDK that introduced both APIs, using an internal API would no longer be needed.</div><div><br></div><div>What do you think about the suggestion?</div><div>Thanks, Rafael<br></div><div><br></div><div>abc<br></div></div>