Explaining invokedynamic. Bootstrap method. Part III
This is third part of mini-series of Explaining invokedynamic. This is the full list of all articles:
Number multiplication (almost) complete example. Part IV
Dynamical hashCode implementation. Part V
Note: All code here is tested on Java 8, but the code should be able to be run in classpath on later version also (with some minor tweaks, that I’m describing).
invokedynamic
Let’s make quick recap:
invokestatic
bytecode is emitted by compiler to link to “correct” static function (Java support function overloading). After bytecode is emitted this function will be always invoked.
invokevirtual
bytecode is emitted by compiler to link to “correct” static method (Java support single dispatching). Also, we’re passing the object upon the method is called as implicit first parameter (this) to the method.
MethodHandle
is such an Object which stores the metadata about the method (or similar low-level operation), such as the name of the method signature of the method etc. One way took on it is a destination of the pointer to method (de-referenced method (constructor, field, or similar low-level operation)).
A CallSite
is a holder for a variable MethodHandle.
One way took on it is pointer to the method (or similar low-level operation). This pointer can change overtime.
Quote:
Before Java 7, the JVM only had four method invocation types:
invokevirtual
to call normal class methods,invokestatic
to call static methods,invokeinterface
to call interface methods, andinvokespecial
to call constructors or private methods.Despite their differences, all these invocations share one simple trait: They have a few predefined steps to complete each method call, and we can’t enrich these steps with our custom behaviors.
There are two main workarounds for this limitation: One at compile-time and the other at runtime. The former is usually used by languages like Scala or Koltin and the latter is the solution of choice for JVM-based dynamic languages like JRuby.
The runtime approach is usually reflection-based and consequently, inefficient.
On the other hand, the compile-time solution is usually relying on code-generation at compile-time. This approach is more efficient at runtime. However, it’s somewhat brittle and also may cause a slower startup time as there’s more bytecode to process.
…
invokedynamic lets us bootstrap the method invocation process in any way we want. That is, when the JVM sees an invokedynamic opcode for the first time, it calls a special method known as the bootstrap method to initialize the invocation process:
The bootstrap method is a normal piece of Java code that we’ve written to set up the invocation process. Therefore, it can contain any logic.
Once the bootstrap method completes normally, it should return an instance of CallSite. This CallSite encapsulates the following pieces of information:
* A pointer to the actual logic that JVM should execute. This should be represented as a MethodHandle.
* A condition representing the validity of the returned CallSite.
From now on, every time JVM sees this particular opcode again, it will skip the slow path and directly calls the underlying executable. Moreover, the JVM will continue to skip the slow path until the condition in the CallSite changes.
As opposed to the Reflection API, the JVM can completely see-through MethodHandles and will try to optimize them, hence the better performance.
https://www.baeldung.com/java-invoke-dynamic
If it sounds too abstract, don’t worry. I’m going to continue with concrete example.
This is third part of mini-series of Explaining invokedynamic. This is the full list of all articles:
Number multiplication (almost) complete example. Part IV
Dynamical hashCode implementation. Part V