The only ways to perform byte code transformation without an instance of an Instrumentation
implementation would be
Custom class loaders which can change the bytes before calling defineClass
(which is limited to classes loaded through that loader)
Calling MethodHandles.Lookup.defineClass
with modified bytes even before the class has been loaded, which works on the widespread JVMs with lazy loading, but is limited to your own module or modules opened to your module
Neither approach can change already loaded classes. That requires the Instrumentation
reference and the only place where a JVM ever hands out such reference, are the initialization methods of Java Agents. So to use it, a Java Agent is unavoidable, even if it may be just a stub storing the reference, for use by your application code.
Note that starting with Java 9, there is the Launcher-Agent-Class
manifest attribute for jar files that can specify the class of a Java Agent to start before the class specified with the Main-Class
is launched. That way, you can easily have your Agent collaborating with your application code in your JVM, without the need for any additional command line options. The Agent can be as simple as having an agentmain
method in your main class storing the Instrumentation
reference in a static variable.
See the java.lang.instrument
package documentation…
Getting hands on an Instrumentation
instance when the JVM has not been started with Agents is trickier. It must support launching Agents after startup in general, e.g. via the Attach API. This answer demonstrates at its end such a self-attach to get hands on the Instrumentation
. When you have the necessary manifest attribute in your application jar file, you could even use that as agent jar and omit the creation of a temporary stub file.
However, recent JVMs forbid self-attaching unless -Djdk.attach.allowAttachSelf=true
has been specified at startup, but I suppose, taking additional steps at startup time, is precisely what you don’t want to do. One way to circumvent this, is to use another process. All this process has to to, is to attach to your original process and tell the JVM to start the Agent. Then, it may already terminate and everything else works the same way as before the introduction of this restriction.
As mentioned in this comment, Byte-Buddy has already implemented those necessary steps and the stripped-down Byte-Buddy-Agent contains that logic only, so you can use it to build your own logic atop it.