invokedynamic最初的工作至少始于2007年,而第一次成功的動(dòng)態(tài)調(diào)用發(fā)生在2008年8月26日犁苏。這比Oracle收購Sun還要早硬萍,按照大多數(shù)開發(fā)人員的標(biāo)準(zhǔn),這個(gè)特性的研發(fā)已經(jīng)持續(xù)了相當(dāng)長的時(shí)間围详。
值得注意的是朴乖,從Java 1.0到現(xiàn)在,invokedynamic是第一個(gè)新加入的Java字節(jié)碼助赞,它與已有的字節(jié)碼invokevirtual买羞、invokestatic、invokeinterface和invokespecial組合在了一起雹食。已有的這四個(gè)操作碼實(shí)現(xiàn)了Java開發(fā)人員所熟知的所有形式的方法分派(dispatch):
invokevirtual——對實(shí)例方法的標(biāo)準(zhǔn)分派
invokestatic——用于分派靜態(tài)方法
invokeinterface——用于通過接口進(jìn)行方法調(diào)用的分派
invokespecial——當(dāng)需要進(jìn)行非虛(也就是“精確”)分派時(shí)會(huì)用到
對Java方法的所有調(diào)用都編譯成了四個(gè)操作碼中的某一個(gè)畜普,那么問題就來了——invokedynamic是做什么的,它對于Java開發(fā)人員有什么用處呢群叶?
這個(gè)特性的主要目標(biāo)在于創(chuàng)建一個(gè)字節(jié)碼漠嵌,用于處理新型的方法分派——它的本質(zhì)是允許應(yīng)用級(jí)別的代碼來確定執(zhí)行哪一個(gè)方法調(diào)用,只有在調(diào)用要執(zhí)行的時(shí)候盖呼,才會(huì)進(jìn)行這種判斷。這樣的話化撕,相對于Java平臺(tái)之前所提供的編程風(fēng)格几晤,允許語言和框架的編寫人員支持更加動(dòng)態(tài)的編碼風(fēng)格。
它的目的在于由用戶代碼通過方法句柄API(method handles API)在運(yùn)行時(shí)確定如何分派植阴,同時(shí)避免反射帶來的性能懲罰和安全問題蟹瘾。實(shí)際上圾浅,invokedynamic所宣稱的目標(biāo)就是一旦該特性足夠成熟,它的速度要像常規(guī)的方法分派(invokevirtual)一樣快憾朴。
當(dāng)Java 7發(fā)布的時(shí)候狸捕,JVM就已經(jīng)支持執(zhí)行新的字節(jié)碼了,但是不管提交什么樣的Java代碼众雷,javac都不會(huì)產(chǎn)生包含invokedynamic的字節(jié)碼灸拍。這項(xiàng)特性用來支持JRuby和其他運(yùn)行在JVM上的動(dòng)態(tài)語言。
在Java 8中砾省,這發(fā)生了變化鸡岗,在實(shí)現(xiàn)lambda表達(dá)式和默認(rèn)方法時(shí),底層會(huì)生成和使用invokedynamic编兄,它同時(shí)還會(huì)作為Nashorn的首選分派機(jī)制轩性。但是,對于Java應(yīng)用的開發(fā)人員來說狠鸳,依然沒有直接的方式實(shí)現(xiàn)完全的動(dòng)態(tài)方法處理(resolution)揣苏。也就是說,Java語言并沒有提供關(guān)鍵字或庫來創(chuàng)建通用的invokedynamic調(diào)用點(diǎn)(call site)件舵。這意味著卸察,盡管這種機(jī)制的功能非常強(qiáng)大,但它對于大多數(shù)的Java開發(fā)人員來說依然有些陌生芦圾。
inDy(invokedynamic)是 java 7 引入的一條新的虛擬機(jī)指令蛾派,這是自 1.0 以來第一次引入新的虛擬機(jī)指令。到了 java 8 這條指令才第一次在 java 應(yīng)用个少,用在 lambda 表達(dá)式中洪乍。 indy 與其他 invoke 指令不同的是它允許由應(yīng)用級(jí)的代碼來決定方法解析。所謂應(yīng)用級(jí)的代碼其實(shí)是一個(gè)方法夜焦,在這里這個(gè)方法被稱為引導(dǎo)方法(Bootstrap Method)壳澳,簡稱 BSM。BSM 返回一個(gè) CallSite(調(diào)用點(diǎn)) 對象茫经,這個(gè)對象就和 inDy 鏈接在一起了巷波。以后再執(zhí)行這條 inDy 指令都不會(huì)創(chuàng)建新的 CallSite 對象。CallSite 就是一個(gè) MethodHandle(方法句柄)的 holder卸伞。方法句柄指向一個(gè)調(diào)用點(diǎn)真正執(zhí)行的方法抹镊。
方法句柄簡介
要讓invokedynamic正常運(yùn)行,一個(gè)核心的概念就是方法句柄(method handle)荤傲。它代表了一個(gè)可以從invokedynamic調(diào)用點(diǎn)進(jìn)行調(diào)用的方法垮耳。這里的基本理念就是每個(gè)invokedynamic指令都會(huì)與一個(gè)特定的方法關(guān)聯(lián)(也就是引導(dǎo)方法或BSM)。當(dāng)解釋器(interpreter)遇到invokedynamic指令的時(shí)候,BSM會(huì)被調(diào)用终佛。它會(huì)返回一個(gè)對象(包含了一個(gè)方法句柄,方法句柄類似函數(shù)指針)俊嗽,這個(gè)對象表明了調(diào)用點(diǎn)要實(shí)際執(zhí)行哪個(gè)方法。
在一定程度上铃彰,這與反射有些類似绍豁,但是反射有它的局限性,這些局限性使它不適合與invokedynamic協(xié)作使用牙捉。Java 7 API中加入了java.lang.invoke.MethodHandle(及其子類)竹揍,通過它們來代表invokedynamic指向的方法。為了實(shí)現(xiàn)操作的正確性鹃共,MethodHandle會(huì)得到JVM的一些特殊處理鬼佣。
方法類型
一個(gè)Java方法可以視為由四個(gè)基本內(nèi)容所構(gòu)成:
名稱
簽名(包含返回類型)
定義它的類
實(shí)現(xiàn)方法的字節(jié)碼
方法句柄首先需要的一個(gè)構(gòu)建塊就是表達(dá)方法簽名的方式,以便于查找霜浴。在Java 7引入的Method Handles API中晶衷,這個(gè)角色是由java.lang.invoke.MethodType類來完成的,它使用一個(gè)不可變的實(shí)例來代表簽名阴孟。要獲取MethodType晌纫,我們可以使用methodType()工廠方法。這是一個(gè)參數(shù)可變(variadic)的方法永丝,以class對象作為參數(shù)锹漱。
第一個(gè)參數(shù)所使用的class對象,對應(yīng)著簽名的返回類型慕嚷;剩余參數(shù)中所使用的class對象哥牍,對應(yīng)著簽名中方法參數(shù)的類型。例如:
//toString()的簽名
MethodType mtToString = MethodType.methodType(String.class);
// setter方法的簽名
MethodType mtSetter = MethodType.methodType(void.class, Object.class);
// Comparator中compare()方法的簽名
MethodType mtStringComparator = MethodType.methodType(int.class, String.class, String.class);
現(xiàn)在我們就可以使用MethodType喝检,再組合方法名稱以及定義方法的類來查找方法句柄嗅辣。要實(shí)現(xiàn)這一點(diǎn),我們需要調(diào)用靜態(tài)的MethodHandles.lookup()方法挠说。這樣的話澡谭,會(huì)給我們一個(gè)“查找上下文(lookup context)”,這個(gè)上下文基于當(dāng)前正在執(zhí)行的方法(也就是調(diào)用lookup()的方法)的訪問權(quán)限损俭。
查找上下文對象有一些以“find”開頭的方法蛙奖,例如,findVirtual()杆兵、findConstructor()雁仲、findStatic()等。這些方法將會(huì)返回實(shí)際的方法句柄琐脏,需要注意的是攒砖,只有在創(chuàng)建查找上下文的方法能夠訪問(調(diào)用)被請求方法的情況下,才會(huì)返回句柄
public MethodHandle getToStringMH() {
MethodHandle mh = null;
MethodType mt = MethodType.methodType(String.class);
MethodHandles.Lookup lk = MethodHandles.lookup();
try {
mh = lk.findVirtual(getClass(), "toString", mt);
} catch (NoSuchMethodException | IllegalAccessException mhx) {
throw (AssertionError)new AssertionError().initCause(mhx);
}
return mh;
}
MethodHandle中有兩個(gè)方法能夠觸發(fā)對方法句柄的調(diào)用,那就是invoke()和invokeExact()祭衩。這兩個(gè)方法都是以接收者(receiver)和調(diào)用變量作為參數(shù),所以它們的簽名為:
public final Object invoke(Object... args) throws Throwable;
public final Object invokeExact(Object... args) throws Throwable;
兩者的區(qū)別在于阅签,invokeExact()在調(diào)用方法句柄時(shí)會(huì)試圖嚴(yán)格地直接匹配所提供的變量掐暮。而invoke()與之不同,在需要的時(shí)候政钟,invoke()能夠稍微調(diào)整一下方法的變量路克。invoke()會(huì)執(zhí)行一個(gè)asType()轉(zhuǎn)換,它會(huì)根據(jù)如下的這組規(guī)則來進(jìn)行變量的轉(zhuǎn)換:
如果需要的話养交,原始類型會(huì)進(jìn)行裝箱操作
如果需要的話精算,裝箱后的原始類型會(huì)進(jìn)行拆箱操作
如果必要的話,原始類型會(huì)進(jìn)行擴(kuò)展
void返回類型會(huì)轉(zhuǎn)換為0(對于返回原始類型的情況)碎连,而對于預(yù)期得到引用類型的返回值的地方灰羽,將會(huì)轉(zhuǎn)換為null
null值會(huì)被視為正確的,不管靜態(tài)類型是什么都可以進(jìn)行傳遞
接下來鱼辙,我們看一下考慮上述規(guī)則的簡單調(diào)用樣例:
Object rcvr = "a";
try {
MethodType mt = MethodType.methodType(int.class);
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle mh = l.findVirtual(rcvr.getClass(), "hashCode", mt);
int ret;
try {
ret = (int)mh.invoke(rcvr);
System.out.println(ret);
} catch (Throwable t) {
t.printStackTrace();
}
} catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
在更為復(fù)雜的樣例中廉嚼,方法句柄能夠以更清晰的方式來執(zhí)行與核心反射功能相同的動(dòng)態(tài)編程任務(wù)。除此之外倒戏,在設(shè)計(jì)之初怠噪,方法句柄就與JVM底層的執(zhí)行模型協(xié)作地更好.
方法句柄與invokedynamic
invokedynamic指令通過引導(dǎo)方法(bootstrap method,BSM)機(jī)制來使用方法句柄杜跷。與invokevirtual指令不同傍念,invokedynamic指令沒有接收者對象。相反葛闷,它們的行為類似于invokestatic憋槐,會(huì)使用BSM來返回一個(gè)CallSite類型的對象。這個(gè)對象包含一個(gè)方法句柄(稱之為“target”)孵运,它代表了當(dāng)前invokedynamic指令要執(zhí)行的方法秦陋。
當(dāng)包含invokedynamic的類加載時(shí),調(diào)用點(diǎn)會(huì)處于“unlaced”狀態(tài)治笨,在BSM返回之后驳概,得到的CallSite和方法句柄會(huì)讓調(diào)用點(diǎn)處于“l(fā)aced”狀態(tài)。
BSM的簽名大致會(huì)如下所示(注意旷赖,BSM的名稱是任意的):
static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type);
如果你希望創(chuàng)建包含invokedynamic的代碼顺又,那么我們需要使用一個(gè)字節(jié)碼操縱庫(因?yàn)镴ava語言本身并不包含我們所需的構(gòu)造)。在本文剩余的內(nèi)容中等孵,我們將會(huì)使用ASM庫來生成包含invokedynamic指令的字節(jié)碼稚照。從Java應(yīng)用程序的角度來看,它們看起來就像是常規(guī)的類文件(當(dāng)然,它們沒有相關(guān)的Java源碼表述)果录。Java代碼會(huì)將其視為“黑盒”上枕,不過我們可以調(diào)用方法并使用invokedynamic及其相關(guān)的功能。
下面弱恒,我們來看一下基于ASM的類辨萍,它會(huì)使用invokedynamic指令來生成“Hello World”。
package jvm.excecution_engine;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static jdk.internal.org.objectweb.asm.Opcodes.V1_7;
import static jdk.nashorn.internal.runtime.regexp.joni.constants.StackType.RETURN;
import static sun.tools.java.RuntimeConstants.ACC_PUBLIC;
public class InvokeDynamicCreator {
public static void main(final String[] args) throws Exception {
final String outputClassName = "Dynamic";
try (FileOutputStream fos
= new FileOutputStream(new File("./" + outputClassName + ".class"))) {
fos.write(dump(outputClassName, "bootstrap", "()V"));
}
}
public static byte[] dump(String outputClassName, String bsmName, String targetMethodDescriptor)
throws Exception {
final ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
// 為引導(dǎo)類搭建基本的元數(shù)據(jù)
cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, outputClassName, null, "java/lang/Object", null);
// 創(chuàng)建標(biāo)準(zhǔn)的void構(gòu)造器
mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 創(chuàng)建標(biāo)準(zhǔn)的main方法
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
MethodType.class);
Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, "kathik/InvokeDynamicCreator", bsmName,
mt.toMethodDescriptorString());
mv.visitInvokeDynamicInsn("runDynamic", targetMethodDescriptor, bootstrap);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
private static void targetMethod() {
System.out.println("Hello World!");
}
public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
final MethodHandles.Lookup lookup = MethodHandles.lookup();
// 需要使用lookupClass()返弹,因?yàn)檫@個(gè)方法是靜態(tài)的
final Class currentClass = lookup.lookupClass();
final MethodType targetSignature = MethodType.methodType(void.class);
final MethodHandle targetMH = lookup.findStatic(currentClass, "targetMethod", targetSignature);
return new ConstantCallSite(targetMH.asType(type));
}
}
這個(gè)代碼分為兩部分锈玉,第一部分使用ASM Visitor API來創(chuàng)建名為Dynamic的類文件。注意义起,核心的調(diào)用是visitInvokeDynamicInsn()拉背。第二部分包含了要捆綁到調(diào)用點(diǎn)中的目標(biāo)方法,并且還包括invokedynamic指令所需的BSM默终。
注意椅棺,上述的方法是位于InvokeDynamicCreator類中的,而不是所生成的kathik.Dynamic類的一部分穷蛹。這意味著土陪,在運(yùn)行時(shí),InvokeDynamicCreator必須也要和kathik.Dynamic一起位于類路徑中肴熏,否則的話鬼雀,就會(huì)無法找到方法。
當(dāng)InvokeDynamicCreator運(yùn)行時(shí)蛙吏,它會(huì)創(chuàng)建一個(gè)新的類文件Dynamic.class源哩,這個(gè)文件中包含了invokedynamic指令,通過在這個(gè)類上執(zhí)行javap鸦做,我們可以看到這一點(diǎn):
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokedynamic #20, 0 // InvokeDynamic #0:runDynamic:()V
5: return
這個(gè)樣例闡述了invokedynamic最簡單的使用場景励烦,它會(huì)使用一個(gè)特定的常量CallSite對象。這意味著BSM(和lookup)只會(huì)執(zhí)行一次泼诱,所以后續(xù)的調(diào)用會(huì)很快坛掠。
java8 lambda 表達(dá)式
lambda 表達(dá)式 是怎么使用 inDy 呢?以一段簡單的代碼為例
public class LambdaTest {
public static void main(String[] args) {
Runnable r = () -> System.out.println(Arrays.toString(args));
r.run();
}
}
用 javap -v -p LambdaTest 查看字節(jié)碼治筒,可以發(fā)現(xiàn)寥寥幾行 java 代碼生成的字節(jié)碼卻不少屉栓,單單常量池常量就有 66 個(gè)之多。
Classfile ./com/company/LambdaTest.class
Last modified 2017-10-11; size 1296 bytes
MD5 checksum 7ac0bf40633d31a57673577fde0a699d
Compiled from "LambdaTest.java"
public class com.company.LambdaTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#25 // java/lang/Object."<init>":()V
#2 = InvokeDynamic #0:#30 // #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
#3 = InterfaceMethodref #31.#32 // java/lang/Runnable.run:()V
#4 = Fieldref #33.#34 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #35.#36 // java/util/Arrays.toString:([Ljava/lang/Object;)Ljava/lang/String;
#6 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #39 // com/company/LambdaTest
#8 = Class #40 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/company/LambdaTest;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 r
#21 = Utf8 Ljava/lang/Runnable;
#22 = Utf8 lambda$main$0
#23 = Utf8 SourceFile
#24 = Utf8 LambdaTest.java
#25 = NameAndType #9:#10 // "<init>":()V
#26 = Utf8 BootstrapMethods
#27 = MethodHandle #6:#41 // 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;
#28 = MethodType #10 // ()V
#29 = MethodHandle #6:#42 // invokestatic com/company/LambdaTest.lambda$main$0:([Ljava/lang/String;)V
#30 = NameAndType #43:#44 // run:([Ljava/lang/String;)Ljava/lang/Runnable;
#31 = Class #45 // java/lang/Runnable
#32 = NameAndType #43:#10 // run:()V
#33 = Class #46 // java/lang/System
#34 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#35 = Class #49 // java/util/Arrays
#36 = NameAndType #50:#51 // toString:([Ljava/lang/Object;)Ljava/lang/String;
#37 = Class #52 // java/io/PrintStream
#38 = NameAndType #53:#54 // println:(Ljava/lang/String;)V
#39 = Utf8 com/company/LambdaTest
#40 = Utf8 java/lang/Object
#41 = Methodref #55.#56 // 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;
#42 = Methodref #7.#57 // com/company/LambdaTest.lambda$main$0:([Ljava/lang/String;)V
#43 = Utf8 run
#44 = Utf8 ([Ljava/lang/String;)Ljava/lang/Runnable;
#45 = Utf8 java/lang/Runnable
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 java/util/Arrays
#50 = Utf8 toString
#51 = Utf8 ([Ljava/lang/Object;)Ljava/lang/String;
#52 = Utf8 java/io/PrintStream
#53 = Utf8 println
#54 = Utf8 (Ljava/lang/String;)V
#55 = Class #58 // java/lang/invoke/LambdaMetafactory
#56 = NameAndType #59:#63 // 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;
#57 = NameAndType #22:#17 // lambda$main$0:([Ljava/lang/String;)V
#58 = Utf8 java/lang/invoke/LambdaMetafactory
#59 = Utf8 metafactory
#60 = Class #65 // java/lang/invoke/MethodHandles$Lookup
#61 = Utf8 Lookup
#62 = Utf8 InnerClasses
#63 = Utf8 (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;
#64 = Class #66 // java/lang/invoke/MethodHandles
#65 = Utf8 java/lang/invoke/MethodHandles$Lookup
#66 = Utf8 java/lang/invoke/MethodHandles
{
public com.company.LambdaTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/company/LambdaTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: invokedynamic #2, 0 // InvokeDynamic #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
6: astore_1
7: aload_1
8: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V
13: return
LineNumberTable:
line 8: 0
line 9: 7
line 10: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 args [Ljava/lang/String;
7 7 1 r Ljava/lang/Runnable;
private static void lambda$main$0(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokestatic #5 // Method java/util/Arrays.toString:([Ljava/lang/Object;)Ljava/lang/String;
7: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
}
SourceFile: "LambdaTest.java"
InnerClasses:
public static final #61= #60 of #64; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 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:
#28 ()V
#29 invokestatic com/company/LambdaTest.lambda$main$0:([Ljava/lang/String;)V
#28 ()V
可以發(fā)現(xiàn)多出了一個(gè)新方法耸袜,方法體就是 lambda 體(lambda body)友多,轉(zhuǎn)換為源碼如下:
private static void lambda$main$0(java.lang.String[] args){
System.out.println(Arrays.toString(args));
}
主要看一下 main 方法,并沒有直接調(diào)用上面的方法堤框,而是出現(xiàn)一條 inDy 指令:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: invokedynamic #2, 0 // InvokeDynamic #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
6: astore_1
7: aload_1
8: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V
13: return
可以看到 inDy 指向一個(gè)類型為 CONSTANT_InvokeDynamic_info 的常量項(xiàng) #2
域滥,另外 0
是預(yù)留參數(shù)纵柿,暫時(shí)沒有作用。
#2 = InvokeDynamic #0:#30 // #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
#0
表示在 Bootstrap methods 表中的索引:
BootstrapMethods:
0: #27 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:
#28 ()V
#29 invokestatic com/company/LambdaTest.lambda$main$0:([Ljava/lang/String;)V
#28 ()V
#30
則是一個(gè) CONSTANT_NameAndType_info启绰,表示方法名和方法類型(返回值和參數(shù)列表)昂儒,這個(gè)會(huì)作為參數(shù)傳遞給 BSM。
#30 = NameAndType #43:#44 // run:([Ljava/lang/String;)Ljava/lang/Runnable;
再看回表中的第 0 項(xiàng)委可,#27
是一個(gè) [CONSTANT_MethodHandle_info荆忍,實(shí)際上是個(gè) MethodHandle(方法句柄)對象,這個(gè)句柄指向的就是 BSM 方法。在這里就是:
java.lang.invoke.LambdaMetafactory.metafactory(MethodHandles.Lookup,String,MethodType,MethodType,MethodHandle,MethodType)
BSM 前三個(gè)參數(shù)是固定的州藕,后面還可以附加任意數(shù)量的參數(shù)跨释,但是參數(shù)的類型是有限制的,參數(shù)類型只能是
String
Class
int
long
float
double
MethodHandle
MethodType
LambdaMetafactory.metafactory 帶多三個(gè)參數(shù)
Method arguments:
#25 ()V
#26 invokestatic com/company/LambdaTest.lambda$main$0:()V
#25 ()V
inDy 所需要的數(shù)據(jù)大概就是這些.
inDy 運(yùn)行時(shí)
每一個(gè) inDy 指令都稱為 Dynamic Call Site(動(dòng)態(tài)調(diào)用點(diǎn))黎茎,根據(jù) jvm 規(guī)范所說的,inDy 可以分為兩步,這兩步部分代碼代碼是在 java 層的虎眨,給 metafactory 方法設(shè)斷點(diǎn)可以看到一些行為。
第一步 inDy 需要一個(gè) CallSite(調(diào)用點(diǎn)對象)镶摘,CallSite 是由 BSM 返回的嗽桩,所以這一步就是調(diào)用 BSM 方法。
調(diào)用 BSM 方法可以看作 invokevirtual 指令執(zhí)行一個(gè) invoke 方法凄敢,方法簽名如下:
invoke:(MethodHandle,Lookup,String,MethodType,/*其他附加靜態(tài)參數(shù)*/)CallSite
前四個(gè)參數(shù)是固定的碌冶,被依次壓入操作棧里
MethodHandle,實(shí)際上這個(gè)方法句柄就是指向 BSM
Lookup, 也就是調(diào)用者涝缝,是 Indy 指令所在的類的上下文扑庞,可以通過 Lookup#lookupClass()獲取這個(gè)類
name ,lambda 所實(shí)現(xiàn)的方法名拒逮,也就是"run"
invokedType罐氨,調(diào)用點(diǎn)的方法簽名,這里是 methodType(Runnable.class,String[].class)
接下來就是執(zhí)行 LambdaMetafactory.metafactory
方法了滩援,它會(huì)創(chuàng)建一個(gè)匿名類栅隐,這個(gè)類是通過 ASM 編織字節(jié)碼在內(nèi)存中生成的,然后直接通過 unsafe 直接加載而不會(huì)寫到文件里玩徊。不過可以通過下面的虛擬機(jī)參數(shù)讓它運(yùn)行的時(shí)候輸出到文件
-Djdk.internal.lambda.dumpProxyClasses=<path>
這個(gè)類是根據(jù) lambda 的特點(diǎn)生成的租悄,輸出后可以看到,在這個(gè)例子中是這樣的:
import java.lang.invoke.LambdaForm.Hidden;
// $FF: synthetic class
final class LambdaTest$$Lambda$1 implements Runnable {
private final String[] arg$1;
private LambdaTest$$Lambda$1(String[] var1) {
this.arg$1 = var1;
}
private static Runnable get$Lambda(String[] var0) {
return new LambdaTest$$Lambda$1(var0);
}
@Hidden
public void run() {
LambdaTest.lambda$main$0(this.arg$1);
}
}
然后就是創(chuàng)建一個(gè) CallSite佣赖,綁定一個(gè) MethodHandle恰矩,指向的方法其實(shí)就是生成的類中的靜態(tài)方法 LambdaTest$$LambdaLambda(String[])Runnable。然后把調(diào)用點(diǎn)對象返回憎蛤,到這里 BSM 方法執(zhí)行完畢外傅。
更詳細(xì)的可參考:
- 淺談Lambda Expression - 簡書
- [Java] 關(guān)于OpenJDK對Java 8 lambda表達(dá)式的運(yùn)行時(shí)實(shí)現(xiàn)的查看方式 - 知乎專欄
第二步纪吮,就是執(zhí)行這個(gè)方法句柄了,這個(gè)過程就像 invokevirtual 指令執(zhí)行 MethodHandle#invokeExact 一樣萎胰,
加上 inDy 上面那一條 aload_0 指令碾盟,這是操作數(shù)棧有兩個(gè)分別是:
- args[],lambda 里面調(diào)用了 main 方法的參數(shù)
- 調(diào)用點(diǎn)對象(CallSite)技竟,實(shí)際上是方法句柄冰肴。如果是 CostantCallSite 的時(shí)候,inDy 會(huì)直接跟他的方法句柄鏈接榔组。
傳入 args熙尉,執(zhí)行方法,返回一個(gè) Runnable 對象搓扯,壓入棧頂检痰。到這里 inDy 就執(zhí)行完畢。
接下來的指令就很好理解锨推,astore_1 把棧頂?shù)?Runnable 對象放到局部變量表的槽位1铅歼,也是變量 r。剩下的就是再拿出來調(diào)用 run 方法换可。