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_Vcontains:- an object before the
F: this is the MethodHandlethisinstance, 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 theinvokehandlebytecode 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_Vcontains:- an object before the
F: this is theMemberNamethat represents the target method (it basically carries a C++Methodpointer 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
Fin our example) - followed by the
MemberNameof 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
MemberNamegets 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.