Lambda Notes 2 - MethodHandle
(Last updated Sep 2, 2018)
Hello MethodHandle
To understand what’s happening with the extra-long parameter to invokedynamic
from
my last post, let’s take a detour in
MethodHandle.
The main reasons that I talk about MethodHandles here are:
- Their implementation is similar to, but not as messy as, Lambdas. So it will be like a practice run before dealing with the real thing.
- Lambda is implemented using MethodHandles, so let’s first understand how the latter works.
$ cat HelloMH.java
import java.lang.invoke.*;
public class HelloMH {
public static void main(String ...args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(void.class, float.class);
MethodHandle mh = lookup.findStatic(HelloMH.class, "callme", mt);
mh.invokeExact(4.0f);
}
private static void callme(float x) {
System.out.println("Hello MH.invoke: " + x);
Thread.dumpStack();
}
}
----
$ javac HelloMH.java
$ javap -c HelloMH.class
public class HelloMH {
...
public static void main(java.lang.String[]) throws java.lang.Throwable;
Code:
0: invokestatic #2 // java/lang/invoke/MethodHandles.lookup:()\
// Ljava/lang/invoke/MethodHandles$Lookup;
3: astore_1
4: getstatic #3 // java/lang/Void.TYPE:Ljava/lang/Class;
7: getstatic #4 // Field java/lang/Float.TYPE:Ljava/lang/Class;
10: invokestatic #5 // java/lang/invoke/MethodType.methodType:\
// (Ljava/lang/Class;Ljava/lang/Class;)\
// Ljava/lang/invoke/MethodType;
13: astore_2
14: aload_1
15: ldc #6 // class HelloMH
17: ldc #7 // String callme
19: aload_2
20: invokevirtual #8 // java/lang/invoke/MethodHandles$Lookup.findStatic:\
// (Ljava/lang/Class;Ljava/lang/String;\
// Ljava/lang/invoke/MethodType;)\
// Ljava/lang/invoke/MethodHandle;
23: astore_3
24: aload_3 // the MethodHandle -- 1st call parameter
25: ldc #9 // float 4.0f -- 2nd call parameter
26: invokevirtual #10 // java/lang/invoke/MethodHandle.invokeExact:\
// (F)V
30: return
}
----
$ java -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames -cp . HelloMH
Hello MH.invoke: 4.0
java.lang.Exception: Stack trace
at java.base/java.lang.Thread.dumpStack(Thread.java:1434)
at HelloMH.callme(HelloMH.java:13)
at java.base/java.lang.invoke.LambdaForm$DMH/0x00000007c0060840\
.invokeStatic(LambdaForm$DMH:1000010)
at java.base/java.lang.invoke.LambdaForm$MH/0x00000007c0061040\
.invokeExact_MT(LambdaForm$MH:1000019)
at HelloMH.main(HelloMH.java:8)
Note: The /0x00000007c0061040
notation printed in the stack trace indicate that LambdaForm$DMH
and LambdaForm$MH
are loaded as JVM anonymous classes
(which are not to be confused by
anonymous classes in the Java source code, ugh!).
For breveity, I will omit such notations in the rest of this page.
Compilation of MethodHandle.invokeExact by javac
MethodHandle.invokeExact
looks similar to
Method.invoke
in the Java Reflection
API. However,
it’s compiled very differently by javac.
Even though the argument type of MethodHandle.invokeExact
is
void (Object...)
, the bytecodes emitted by javac looks as
if you were calling the virtual method void
MethodHandle.invokeExact(float)
, which doesn’t exist!
$ javap java.lang.invoke.MethodHandle | grep invokeExact
public final native java.lang.Object invokeExact(java.lang.Object...)
throws java.lang.Throwable;
$ javap -private -c HelloMH.class
...
23: aload_3 // local 3 is the <mh> variable
24: ldc #9 // float 4.0f
26: invokevirtual #10 // Method java/lang/invoke/MethodHandle.invokeExact:\
// (F)V
...
To understand what’s happening, see the section Method handle
compilation in the MethodHandle
reference. You
can also see the reference to the @PolymorphicSignature
annotation
in the MethodHandle.invokeExact()
source code.
Execution of MethodHandle.invokeExact
To see how MethodHandle.invokeExact
actually calls the target method, let’s look at the stack trace from above.
Even though the bytecode is invokevirtual MethodHandle.invokeExact(...)
, the call frame for
invokeExact
is missing in the call stack, and is magically replaced by a call to a dynamically
generated method java.lang.invoke.LambdaForm$MH.invokeExact_MT()
.
To see the contents of the generated classes, use
-Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true
. (Note that
for ease of debugging, when this flag is specified, the names of the
LambdaForm$MH
class is changed to LambdaForm$MH000
)
$ java -Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true \
-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames \
-cp . HelloMH
<... snip...>
Hello MH.invoke: 4.0
java.lang.Exception: Stack trace
at java.base/java.lang.Thread.dumpStack(Thread.java:1434)
at HelloMH.callme(HelloMH.java:13)
at java.base/java.lang.invoke.LambdaForm$DMH000
.invokeStatic000_LF_V()
at java.base/java.lang.invoke.LambdaForm$MH000
.invokeExact_MT000_LFL_V()
at HelloMH.main(HelloMH.java:8)
$ jdis 'DUMP_CLASS_FILES/java/lang/invoke/LambdaForm$MH000.class'
package java/lang/invoke;
super final class LambdaForm$MH000
version 52:0
{
@+java/lang/invoke/LambdaForm$Hidden { }
@+java/lang/invoke/LambdaForm$Compiled { }
@+jdk/internal/vm/annotation/ForceInline { }
static Method invokeExact_MT000_LFL_V\
:"(Ljava/lang/Object;FLjava/lang/Object;)V"
stack 2 locals 3
{
aload_0;
checkcast class MethodHandle;
dup;
astore_0;
aload_2;
checkcast class MethodType;
invokestatic Method Invokers.checkExactType\
:"(Ljava/lang/invoke/MethodHandle;\
Ljava/lang/invoke/MethodType;)V";
aload_0;
invokestatic Method Invokers.checkCustomized\
:"(Ljava/lang/invoke/MethodHandle;)V";
aload_0;
fload_1;
invokevirtual Method MethodHandle.invokeBasic:"(F)V";
return;
}
static Method dummy:"()V"
stack 1 locals 0
{
ldc String "MH.invokeExact_MT000_LFL_V=Lambda(a0:L,a1:F,a2:L)=>{\n\
t3:V=Invokers.checkExactType(a0:L,a2:L);\n\
t4:V=Invokers.checkCustomized(a0:L);\n\
t5:V=MethodHandle.invokeBasic(a0:L,a1:F);void}";
pop;
return;
}
}
You can see that invokeExact_MT000_LFL_V
first performs a few checks
on its parameters, and then calls
MethodHandle.invokeBasic
. Well, invokeBasic
is another @PolymorphicSignature
method, and the call is
again magically replaced by LambdaForm$DMH000.invokeStatic000_LF_V
:
$ jdis 'DUMP_CLASS_FILES/java/lang/invoke/LambdaForm$DMH000.class'
...
static Method invokeStatic000_LF_V:"(Ljava/lang/Object;F)V"
stack 2 locals 3
{
aload_0;
invokestatic Method DirectMethodHandle.internalMemberName\
:"(Ljava/lang/Object;)Ljava/lang/Object;";
astore_2;
fload_1;
aload_2;
checkcast class MemberName;
invokestatic Method MethodHandle.linkToStatic\
:"(FLjava/lang/invoke/MemberName;)V";
return;
}
As you can expect, the call to MethodHandle.linkToStatic
is yet again some magic inside the JVM. Basically, it’s a native method that knows the invocation target’s Method*
. In our example, linkToStatic
creates a call frame to execute the callme
method. Note that linkToStatic
itself doesn’t appear in the stack trace – it kinds of makes a tail call into callme
.
References: if you’re interested in more details, see this StackOverflow answer and this HotSpot blog page.
Shuffling of Parameters
Our target method has a single declared parameter, a float
, or F
in the
method signatures. However, you can see that some extra parameters are
adding before and after the formal parameters:
- The frame of
invokeExact_MT000_LFL_V
contains:- an object before the
F
: this is the MethodHandlethis
instance, which gets pushed to the frame because we’re making a virtual call on a MethodHandle object. -
an object after the
F
: this is theMethodType
. It’s appended by theinvokehandle
bytecode so we can dynamically check that we are invoking a MethodHandle of the expected type.This dynamic check is actually necessary because you can actually write valid Java source code like this which can only be checked at run time:
import java.lang.invoke.*; public class BadMH { public static void main(String ...args) throws Throwable { MethodHandle mh[] = new MethodHandle[2]; MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodType mt0 = MethodType.methodType(void.class, float.class); mh[0] = lookup.findStatic(BadMH.class, "callme", mt0); MethodType mt1 = MethodType.methodType(void.class, Object.class); mh[1] = lookup.findStatic(BadMH.class, "callme", mt1); for (MethodHandle x : mh) { x.invokeExact(4.0f); } } private static void callme(float x) { System.out.println("Hello MH.invoke: " + x); } private static void callme(Object x) { System.out.println("Hello MH.invoke: " + x); } } $ java -cp . BadMH Hello MH.invoke: 4.0 java.lang.invoke.WrongMethodTypeException: expected (Object)void but found (float)void at java.lang.invoke.Invokers.newWrongMethodTypeException\ (Invokers.java:476) at java.lang.invoke.Invokers.checkExactType(Invokers.java:485) at BadMH.main(BadMH.java:12)
- an object before the
- The frame of
invokeStatic000_LF_V
contains:- an object before the
F
: this is theMemberName
that represents the target method (it basically carries a C++Method
pointer to the target method).
- an object before the
Inside invokeStatic000_LF_V
, we shuffle the parameters such that the incoming call frame of MethodHandle.linkToStatic
contains:
- all the parameters as declared by the target method (a single
F
in our example) - followed by the
MemberName
of the target method.
When the native method MethodHandle.linkToStatic
is entered, the “front” part of the call
stack already contains the exact parameters as needed by the target method.
linkToStatic
simply retrieves the JVM Method
pointer from the MemberName, drops
this last parameter, and jumps to the entry of the target method.
(We’ll see this in more details in the Linking Polymorphic Methods sections below.)
MethodHandle vs varargs
As we saw above, the arguments to MethodHandle.invokeExact
are not passed using the Varargs
convention (unlike java.lang.reflect.Method.invoke
which is Varargs and would create an Object array to collect the arguments, and thus would be slower
and creat lots of garbage.) Here’s an example of varargs for comparison:
public class HelloVarargs {
public static void main(String... args) {
if (args.length == 0) {
main("a", "b"); // compiled as:
// main(new String[] {"a", "b"});
}
}
}
---
$ javap -c HelloVarargs.class
...
public static void main(java.lang.String...);
Code:
0: aload_0
1: arraylength
2: ifne 22
5: iconst_2
6: anewarray #2 // class java/lang/String
^^^^^^^ create a String array of 2 elements
9: dup
10: iconst_0
11: ldc #3 // String a
13: aastore
14: dup
15: iconst_1
16: ldc #4 // String b
18: aastore
19: invokestatic #5 // Method main:([Ljava/lang/String;)V
22: return
Benchmarking MethodHandles
Here’s a benchmark of java.lang.invoke.MethodHandle
vs java.lang.reflect.Method
:
$ cat BenchMH.java
import java.lang.invoke.*;
import java.lang.reflect.*;
public class BenchMH {
static class Helper {
static MethodHandle getHandle() {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(void.class, String.class);
return lookup.findStatic(BenchMH.class, "callme", mt);
} catch (Throwable t) {
t.printStackTrace();
return null;
}
}
static Method getMethod() {
try {
Class[] argTypes = new Class[] { String.class };
return BenchMH.class.getDeclaredMethod("callme", argTypes);
} catch (Throwable t) {
t.printStackTrace();
return null;
}
}
}
static String c;
static final MethodHandle mh = Helper.getHandle();
static final Method m = Helper.getMethod();
public static void main(String args[]) throws Throwable {
int loops = 400 * 1000 * 1000;
try {
loops = Integer.parseInt(args[0]);
} catch (Throwable t) {}
Class[] argTypes = new Class[] { String.class };
Method m = BenchMH.class.getDeclaredMethod("callme", argTypes);
// The second loop (hopefully) excludes warm up time.
for (int x=0; x<2; x++) {
{
long start = System.currentTimeMillis();
loopMH(loops);
long end = System.currentTimeMillis();
System.out.println("MH: loops = " + loops + ": elapsed = "
+ (end - start) + " ms");
}
{
long start = System.currentTimeMillis();
loopReflect(loops);
long end = System.currentTimeMillis();
System.out.println("Reflect: loops = " + (loops) + ": elapsed = "
+ (end - start) + " ms");
}
{
long start = System.currentTimeMillis();
loopDirect(loops);
long end = System.currentTimeMillis();
System.out.println("direct: loops = " + loops + ": elapsed = "
+ (end - start) + " ms");
}
}
}
private static void loopMH(int loops) throws Throwable {
for (int i=0; i<loops; i++) {
mh.invokeExact("yo!");
}
}
private static void loopReflect(int loops) throws Throwable {
for (int i=0; i<loops; i++) {
m.invoke(null, "yo!");
}
}
private static void loopDirect(int loops) {
for (int i=0; i<loops; i++) {
callme("yo!");
}
}
private static void callme(String x) {
c = x;
}
}
$ java BenchMH 100000000
LOOPS ELAPSED
MH: 400000000 443 ms
Reflect: 400000000 1054 ms ... 2.3x slower than MH
direct: 400000000 435 ms
Note that by using static final MethodHandle mh
, we can get the JIT to inline the method handle
invocation, so it’s just as fast as a direct method invocation. But static final
is not
very convenient to use, and the JDK uses the @Stable
annotation to achieve the same result. You can see an example
here.
However, @Stable
can be used only by classes loaded by the bootstrap loader.
Linking Polymorphic Methods in the HotSpot JVM (static invocations)
Let’s look at the easy ones first. A few native polymorphic methods
(declared with the @PolymorphicSignature
annotation) in the MethodHandle
class
are invoked statically by the generated MethodHandle invoker classes:
- invokeGeneric
- invokeBasic
- linkToVirtual
linkToStatic
- linkToSpecial
- linkToInterface
Here’s an example we have seen above:
static Method invokeStatic000_LF_V:"(Ljava/lang/Object;F)V"
...
invokestatic MethodHandle.linkToStatic:"(FLjava/lang/invoke/MemberName;)V";
These calls are resolved and linked conventionally just like other
native static methods. The only interesting part is these methods are
implemented using assembler code, so you won’t see a C function for
Java_java_lang_invoke_MethodHandle_linkToStatic
inside libjava.so.
On x64, the imterpreter’s implementation of MethodHandle.linkToStatic
is
generated around here in MethodHandles::generate_method_handle_interpreter_entry
. It
contains only 10 instructions:
(gdb) x/10i 0x7fffd8e5a920
// pop(rax_temp) -- return address
0x7fffd8e5a920: pop %rax
// pop(rbx_member) -- extract last argument (MemberName)
0x7fffd8e5a921: pop %rbx
// push(rax_temp) -- re-push return address
0x7fffd8e5a922: push %rax
// load_heap_oop(rbx_method, member_vmtarget)
0x7fffd8e5a923: mov 0x24(%rbx),%ebx
0x7fffd8e5a926: shl $0x3,%rbx // decode compressed oop
// movptr(rbx_method, vmtarget_method);
0x7fffd8e5a92a: mov 0x10(%rbx),%rbx
// testptr(rbx, rbx); jcc(Assembler::zero, L_no_such_method);
0x7fffd8e5a92e: test %rbx,%rbx
0x7fffd8e5a931: je 0x7fffd8e5a93a // Lno_such_method
// jmp(Address(method, entry_offset));
0x7fffd8e5a937: jmpq *0x48(%rbx)
// bind(L_no_such_method);
// jump(RuntimeAddress(StubRoutines::throw_AbstractMethodError_entry()));
0x7fffd8e5a93a: jmpq 0x7fffd8e5a280
As discussed previously, linkToStatic
simply pops off the last parameter as a MemberName
,
such that the incoming parameters are exactly what the target method wants, and then branch
to the target method (as a tail call).
Linking Polymorphic Methods in the HotSpot JVM (Virtual Invocations)
Vitrual invocations (using the invokevirtual
or invokespecial
bytecodes)
of polymorphic methods are much more complicated, and involve lots of Java code.
Recall that a polymorphic method such as MethodHandle.invokeExact
is compiled by javac into the
following bytecodes inside the classfile.
23: aload_3 // the MethodHandle -- 1st call parameter
24: ldc #9 // String yo! -- 2nd call parameter
26: invokevirtual #10 // java/lang/invoke/MethodHandle.invokeExact:\
// (Ljava/lang/String;)V
When the classfile is loaded by HotSpot, the invokevirtual
bytecode is rewritten into the internal
invokehandle
bytecode in here:
-> (*opc) = (u1)Bytecodes::_invokehandle; (gdb) where #0 Rewriter::maybe_rewrite_invokehandle() @ rewriter.cpp:240 #1 Rewriter::rewrite_member_reference() @ rewriter.cpp:174 #2 Rewriter::scan_method() @ rewriter.cpp:472 #3 Rewriter::rewrite_bytecodes() @ rewriter.cpp:550 #4 Rewriter::Rewriter() @ rewriter.cpp:590 #5 Rewriter::rewrite() @ rewriter.cpp:572 #6 InstanceKlass::rewrite_class() @ instanceKlass.cpp:655 #7 InstanceKlass::link_class_impl() @ instanceKlass.cpp:605 ...
When the invokehandle
bytecode is executed for the first time, it’s linked here by
calling back into Java.
// call java.lang.invoke.MethodHandleNatives::linkMethod(... String, // MethodType) -> MemberName JavaCallArguments args; args.push_oop(Handle(THREAD, accessing_klass->java_mirror())); args.push_int(ref_kind); args.push_oop(Handle(THREAD, klass->java_mirror())); args.push_oop(name_str); args.push_oop(method_type); args.push_oop(appendix_box); JavaValue result(T_OBJECT); -> JavaCalls::call_static(&result, SystemDictionary::MethodHandleNatives_klass(), vmSymbols::linkMethod_name(), vmSymbols::linkMethod_signature(), &args, CHECK_(empty)); (gdb) where #0 SystemDictionary::find_method_handle_invoker() @ systemDictionary.cpp:2556 #1 LinkResolver::lookup_polymorphic_method() @ linkResolver.cpp:519 #2 LinkResolver::resolve_handle_call() @ linkResolver.cpp:1661 #3 LinkResolver::resolve_invokehandle() @ linkResolver.cpp:1647 #4 LinkResolver::resolve_invoke() @ linkResolver.cpp:1573 #5 InterpreterRuntime::resolve_invokehandle() @ interpreterRuntime.cpp:968 #6 InterpreterRuntime::resolve_from_cache() @ interpreterRuntime.cpp:1016
When the MethodHandleNatives.linkMethod
call completes, a MemberName
is returned (see here).
- The
MemberName
gets decomposed byunpack_method_and_appendix
, - then the information is stored into a CallInfo using
CallInfo::set_handle
, - and the information is finally stored into the constant pool via
ConstantPoolCacheEntry::set_method_handle
At this point, the linking of this polymorphic method call is complete. Subsequently, this invokehandle
bytecode can be executed by loading information about the MethodHandle from the corresponding ConstantPoolCacheEntry
.
TODO …
I can probably include more details here, but I’ll skip them for now …
Summary
Now you know how a MethodHandle is invoked. In the next blog entry, we’ll see how MethodHandles are used by the invokedynamic
bytecode.