當我剛剛開始學習Java的Lambda的時候噪裕,我曾經(jīng)完全誤解了它的實現(xiàn)方式嚎幸,并且前段時間發(fā)現(xiàn)居然有些朋友和當時的我一樣珊随。
比如下面這個例子:
class Foobar {
Runnable foobar() {
return () -> {};
}
}
在最初的時候,我猜想它也許會像這樣實現(xiàn):
class Foobar {
Runnable foobar() {
return new Runnable() {
@Override
public void run() {
return;
}
};
}
}
實際上污淋,并不是顶滩。
當然了,如果真是這樣做了也算是一種實現(xiàn)方式寸爆,但是Java這里做出了更加巧妙的設計礁鲁。
這個函數(shù)在Java8中經(jīng)過編譯后的字節(jié)碼是這樣的:
java.lang.Runnable foobar();
descriptor: ()Ljava/lang/Runnable;
flags:
Code:
stack=1, locals=1, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: areturn
LineNumberTable:
line 3: 0
乍一看似乎莫名其妙,run
方法其實也實現(xiàn)在了這個類里:
private static void lambda$foobar$0();
descriptor: ()V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 3: 0
它們通過InvokeDynamic
這個字節(jié)碼指令以及一個BootstrapMethod才在兩者之間建立起聯(lián)系:
0: #16 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#17 ()V
#18 invokestatic Foobar.lambda$foobar$0:()V
#17 ()V
看到這里的朋友赁豆,大概應該都是用過MethodHandle
的吧仅醇,不然的話上面這段可能不太好理解。
用Java代碼大致模擬一下魔种,運行時的邏輯大概像是這樣:
class Foobar {
@SuppressWarnings("unchecked")
Runnable foobar() throws Throwable {
return (Runnable) LambdaMetafactory.metafactory(
MethodHandles.lookup(),
"run",
MethodType.methodType(Runnable.class),
MethodType.methodType(void.class),
MethodHandles.lookup().findStatic(Foobar.class, "lambda$foobar$0", MethodType.methodType(void.class)),
MethodType.methodType(void.class)
).dynamicInvoker().invoke();
}
private static void lambda$foobar$0() {
System.out.println("Hello");
return;
}
}
事實上析二,JVM的InvokeDynamic
比起Java的Lambda表達式和方法引用表達式有著更為久遠的歷史,詳見JSR-292节预。
InvokeDynamic
這一條指令叶摄,首先會通過一個BootstrapMethod,獲取到一個CallSite
實例安拟,BootstrapMethod至少需要Lookup
准谚、String
、MethodType
三個參數(shù)去扣,以及任意多個靜態(tài)參數(shù),相關細節(jié)見虛擬機規(guī)范invokedynamic樊破。
在LambdaMetafactory
中除了metafactory
之外還有一個altMetafactory
用于更加復雜的Lambda愉棱,本文暫不討論。
此時已經(jīng)可以了解到metafactory
所需的六個參數(shù)分別代表什么哲戚,這里是它在Java8中的源碼奔滑。
這個方法很簡潔,只有短短幾行:
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
validateMetafactoryArgs
是InnerClassLambdaMetafactory
從它的超類AbstractValidatingLambdaMetafactory
中繼承而來的一個方法顺少,其作用只是進行一些校驗操作朋其,如果發(fā)現(xiàn)錯誤就會拋出LambdaConversionException
王浴。它的代碼在這里。
這里是buildCallSite
的源碼梅猿,它大致可以分為三個部分:
@Override
CallSite buildCallSite() throws LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
if (invokedType.parameterCount() == 0 && !disableEagerInitialization) {
// ...
final Constructor<?>[] ctrs = /* ... */;
Object inst = ctrs[0].newInstance();
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
// ...
} else {
// ...
if (!disableEagerInitialization) {
UNSAFE.ensureClassInitialized(innerClass);
}
return new ConstantCallSite(MethodHandles.Lookup.IMPL_LOOKUP.findStatic(innerClass, NAME_FACTORY, invokedType));
// ...
}
}
disableEagerInitialization
與jdk.internal.lambda.disableEagerInitialization
選項有關氓辣。
首先會在spinInnerClass
中定義一個源碼中不存在的類,在這里用到了asm
框架袱蚓。
比方說這個例子:
class Foobar {
Runnable foo() {
return () -> {};
}
Callable<?> bar() {
return () -> this;
}
}
class Main {
public static void main(String[] args) {
new Foobar().foo();
new Foobar().bar();
}
}
可以在運行的時候加上-Djdk.internal.lambda.dumpProxyClasses
這個選項钞啸。
它在運行時定義的兩個類大致是這樣的:
final class Foobar$$Lambda$1 implements java.lang.Runnable
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
// ...
{
private Foobar$$Lambda$1();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #17 // Method Foobar.lambda$foo$0:()V
3: return
RuntimeVisibleAnnotations:
0: #12()
}
final class Foobar$$Lambda$2 implements java.util.concurrent.Callable
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
// ...
{
private final Foobar arg$1;
descriptor: LFoobar;
flags: ACC_PRIVATE, ACC_FINAL
private Foobar$$Lambda$2(Foobar);
descriptor: (LFoobar;)V
flags: ACC_PRIVATE
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #13 // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #15 // Field arg$1:LFoobar;
9: return
private static java.util.concurrent.Callable get$Lambda(Foobar);
descriptor: (LFoobar;)Ljava/util/concurrent/Callable;
flags: ACC_PRIVATE, ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: new #2 // class Foobar$$Lambda$2
3: dup
4: aload_0
5: invokespecial #19 // Method "<init>":(LFoobar;)V
8: areturn
public java.lang.Object call();
descriptor: ()Ljava/lang/Object;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #15 // Field arg$1:LFoobar;
4: invokespecial #27 // Method Foobar.lambda$bar$1:()Ljava/lang/Object;
7: areturn
RuntimeVisibleAnnotations:
0: #22()
}
發(fā)現(xiàn)沒有,這個Runnable
調(diào)用得是Foobar
的靜態(tài)方法喇潘,并且它里面也并不持有外部引用体斩,但是如果使用匿名內(nèi)部類的實現(xiàn)方式,就有可能造成毫無意義的內(nèi)存泄漏颖低。
現(xiàn)在咱們再回過頭來看看buildCallSite
的實現(xiàn)絮吵,它判斷了invokedType.parameterCount()
是否等于零,如果為零說明不會捕獲任何外部變量忱屑,那么這樣的話蹬敲,也就沒有必要每次調(diào)用都為此而創(chuàng)建一個新的對象,但如果不為零的話想幻,則會通過get$Lambda
獲取這個函數(shù)式接口的實例粱栖。
做一個簡單的實驗:
class Main {
public static void main(String[] args) {
assert(new Foobar().foo() == new Foobar().foo());
assert(new Foobar().bar() == new Foobar().bar());
}
}
開啟-ea
選項后,以上程序?qū)⒃诘诙螖嘌詴r崩潰脏毯,因此對于無捕獲的Lambda表達式來說闹究,并不會重復創(chuàng)建對象,而用匿名內(nèi)部類的方式則需要手動實現(xiàn)復用食店。
經(jīng)過這一套流程渣淤,我們終于獲得了CallSite
的實例,它將與這一個位置綁定吉嫩,重復調(diào)用則可以復用這同一個CallSite
价认。
現(xiàn)在可以發(fā)現(xiàn),Java實際對Lambda和方法引用的實現(xiàn)自娩,是經(jīng)過一層一層的封裝用踩,從而優(yōu)化了許多細節(jié),但是還不止如此忙迁。
注意一下我上面說過的話脐彩,Java中InvokeDynamic
的歷史比Lambda更長,JVM是一種語言無關的運行環(huán)境姊扔,只要按照規(guī)范的要求惠奸,編譯出合法的字節(jié)碼,那么無論任何語言都可以在JVM平臺上運行恰梢,包括Groovy這種動態(tài)語言佛南,有了InvokeDynamic
這條指令梗掰,可以讓動態(tài)語言在JVM平臺上發(fā)展得更好。
InvokeDynamic
這條字節(jié)碼指令它最大的意義是嗅回,它使得JVM新語言的實現(xiàn)者獲得了更大的自由及穗,每個語言的設計者可以用自己的方式創(chuàng)建CallSite
,然后通過自己的編譯器去調(diào)用自己所定義的BootstrapMethod妈拌,而在這一整個過程中都不需要JVM為其提供任何額外的支持拥坛。
那么最后,我來編寫一段示例代碼尘分,作為這篇文章的收尾:
import jdk.internal.org.objectweb.asm.*;
import jdk.internal.org.objectweb.asm.tree.*;
import java.lang.instrument.*;
import java.lang.invoke.*;
import java.security.*;
class Main {
public static void main(String[] args) {
getLambda().run();
}
private static native Runnable getLambda();
}
class Premain extends ClassVisitor implements ClassFileTransformer {
public static void premain(String arg, Instrumentation instrumentation) {
instrumentation.addTransformer(new Premain());
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!"Main".equals(className)) {
return classfileBuffer;
}
ClassWriter cw = new ClassWriter(Opcodes.ASM5);
super.cv = cw;
new ClassReader(classfileBuffer).accept(this, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
}
private Premain() {
super(Opcodes.ASM5, null);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if ("getLambda".equals(name)) {
return null;
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
MethodVisitor mv = super.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, "getLambda", "()Ljava/lang/Runnable;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitInvokeDynamicInsn("run", "()Ljava/lang/Runnable;", new Handle(Opcodes.H_INVOKESTATIC, "Premain", "buildCallSite", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"));
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitInsn(Opcodes.ARETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitMaxs(1, 0);
mv.visitEnd();
super.visitEnd();
}
public static CallSite buildCallSite(MethodHandles.Lookup caller, String invokedName, MethodType invokedType) {
return new ConstantCallSite(MethodHandles.constant(Runnable.class, (Runnable) () -> System.out.println("Hello, world!")));
}
}
有興趣的同學猜惋,可以通過靈活使用這一條InvokeDynamic
指令,在JVM這個廣闊的平臺上培愁,定制自己更喜歡的語言著摔。