深入理解Invokedynamic

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$$Lambda1.getLambda(String[])Runnable。然后把調(diào)用點(diǎn)對象返回憎蛤,到這里 BSM 方法執(zhí)行完畢外傅。

更詳細(xì)的可參考:

第二步纪吮,就是執(zhí)行這個(gè)方法句柄了,這個(gè)過程就像 invokevirtual 指令執(zhí)行 MethodHandle#invokeExact 一樣萎胰,

加上 inDy 上面那一條 aload_0 指令碾盟,這是操作數(shù)棧有兩個(gè)分別是:

  1. args[],lambda 里面調(diào)用了 main 方法的參數(shù)
  2. 調(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 方法换可。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椎椰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子沾鳄,更是在濱河造成了極大的恐慌慨飘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件译荞,死亡現(xiàn)場離奇詭異套媚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)磁椒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門堤瘤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浆熔,你說我怎么就攤上這事本辐。” “怎么了医增?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵慎皱,是天一觀的道長。 經(jīng)常有香客問我叶骨,道長茫多,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任忽刽,我火速辦了婚禮天揖,結(jié)果婚禮上夺欲,老公的妹妹穿的比我還像新娘。我一直安慰自己今膊,他們只是感情好些阅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斑唬,像睡著了一般市埋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恕刘,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天缤谎,我揣著相機(jī)與錄音,去河邊找鬼褐着。 笑死弓千,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的献起。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼镣陕,長吁一口氣:“原來是場噩夢啊……” “哼谴餐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呆抑,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤岂嗓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鹊碍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厌殉,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年侈咕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了公罕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耀销,死狀恐怖楼眷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情熊尉,我是刑警寧澤罐柳,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站狰住,受9級(jí)特大地震影響张吉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜催植,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一肮蛹、第九天 我趴在偏房一處隱蔽的房頂上張望勺择。 院中可真熱鬧,春花似錦蔗崎、人聲如沸酵幕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芳撒。三九已至,卻和暖如春未桥,著一層夾襖步出監(jiān)牢的瞬間笔刹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工冬耿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舌菜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓亦镶,卻偏偏與公主長得像日月,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子缤骨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • inDy(invokedynamic)是 java 7 引入的一條新的虛擬機(jī)指令爱咬,這是自 1.0 以來第一次引入新...
    TiouLims閱讀 18,283評(píng)論 3 31
  • 1、實(shí)例解析 先從一個(gè)例子開始: 例子很簡單绊起,定義了一個(gè)函數(shù)式接口Print 精拟,main方法中有兩處代碼以Lamb...
    冰河winner閱讀 1,599評(píng)論 1 1
  • Invokedynamic指令是java7中加入的字節(jié)碼指令,理解這條指令可以讓我們熟悉程序的執(zhí)行流程虱歪,這篇文章將...
    請輸入妮稱閱讀 2,966評(píng)論 0 1
  • 第二部分 自動(dòng)內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,130評(píng)論 0 2
  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分蜂绎。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,062評(píng)論 1 34