Java ASM詳解:MethodVisitor與Opcode(五)invokedynamic、方法句柄、lambda
前四篇專欄已經(jīng)簡(jiǎn)要的描述了常用的字節(jié)碼,這篇專欄將講述Java 7以來最重要的字節(jié)碼之一:invokedynamic。
一.方法句柄(Method Handle)
方法句柄在Java 7時(shí)被引入,位于java.lang.invoke包下。它類似于反射,但與反射不同的是,它的檢查在創(chuàng)建階段就已經(jīng)結(jié)束,而反射需要每次運(yùn)行時(shí)檢查,所以在理論上方法引用更快。
1. 方法類型(Method Type)
方法句柄包含了一個(gè)方法的信息——所在的類、名稱、參數(shù)列表與返回值。為了描述參數(shù)列表與返回值,Java引入了一個(gè)類——即MethodType,來描述它們。
它類似于反射使用的getMethod方法,但是它不僅需要參數(shù)列表,它還需要返回值。創(chuàng)建一個(gè)MethodType可以使用下面的方法:
這些方法中的rtype參數(shù)都代表了返回值類型,ptypes代表了參數(shù)類型。
下面是一個(gè)例子:使用MethodType描述Arrays::binarySearch(Object[], int, int, Object) -> int
2. 從已有的方法中提取方法句柄
為了獲取一個(gè)方法句柄,最簡(jiǎn)單的途徑就是從一個(gè)現(xiàn)成的方法中提取。為了從一個(gè)現(xiàn)成的類中提取一個(gè)方法句柄,我們需要一個(gè)MethodHandles.Lookup對(duì)象,這個(gè)對(duì)象有兩種獲取方法:
Lookup類提供了以下方法用于查找方法句柄對(duì)象:
可以看到,這些find方法都實(shí)現(xiàn)了某個(gè)字節(jié)碼的功能:findStatic與invokestatic進(jìn)行對(duì)應(yīng)、findGetter與getfield對(duì)應(yīng)等。
除了查找方法,從一個(gè)反射對(duì)象反反射也能獲得方法句柄對(duì)象:
下面是使用例:
1) 獲取System::currentTimeMillis() -> long的方法句柄
2) 獲取獲得System.out(java.io.PrintStream)字段的方法句柄
3) 獲取String::<init>()的方法句柄
4) 獲取訪問sun.misc.Unsafe.theUnsafe(Unsafe)字段的方法句柄
3. 自定義方法句柄
方法句柄不止可以通過查找獲取,還可以通過MethodHandles內(nèi)置的一些方法獲取,下面是一部分內(nèi)置的方法句柄生成器:
這些生成器不止包括了基本的創(chuàng)建對(duì)象與對(duì)象操作,還實(shí)現(xiàn)了一部分流程結(jié)構(gòu),也就是說你可以通過MethodHandles“動(dòng)態(tài)”地創(chuàng)建一個(gè)方法片段。
4. 使用方法句柄
說了這么多創(chuàng)建方法句柄的方式,我們?cè)撛趺词褂盟??MethodHandle提供了兩個(gè)方法用于執(zhí)行方法句柄:
這兩種調(diào)用方式的區(qū)別在于參數(shù)的類型轉(zhuǎn)換:invokeExact要求參數(shù)必須準(zhǔn)確對(duì)應(yīng)MethodType定義的參數(shù),而invoke會(huì)進(jìn)行自動(dòng)轉(zhuǎn)換來嘗試對(duì)應(yīng)。
如果無法對(duì)應(yīng)參數(shù),這兩個(gè)方法都會(huì)拋出WrongMethodTypeException。
下面給出一個(gè)例子,使用上面2.1創(chuàng)建的MethodHandle:
有些情況下,我們不需要第一個(gè)參數(shù)變化(實(shí)例方法的調(diào)用對(duì)象/靜態(tài)方法的第一個(gè)參數(shù)),這時(shí)我們可以用bindTo綁定第一個(gè)參數(shù):
下面是使用例:
說完了方法句柄,接下來來看看CallSite。
二. 動(dòng)態(tài)調(diào)用點(diǎn)(CallSite)
CallSite是一個(gè)為了引導(dǎo)invokedynamic字節(jié)碼指向調(diào)用方法的類,通過它的dynamicInvoker方法可以獲取一個(gè)方法句柄,這個(gè)句柄就代表了inDy的目標(biāo)。
非常量動(dòng)態(tài)調(diào)用點(diǎn)允許重新指定調(diào)用目標(biāo),這時(shí)inDy會(huì)對(duì)目標(biāo)進(jìn)行重新連接。
它有三個(gè)子類:ConstantCallSite、MutableCallSite和VolatileCallSite。它們的區(qū)別如下:
ConstantCallSite指向的方法句柄不能修改,也就是永久性的。連接到它的inDy指令會(huì)永遠(yuǎn)綁定這個(gè)方法句柄。
MutableCallSite允許修改指向的方法句柄目標(biāo),指向目標(biāo)的行為類似普通字段。它的目標(biāo)改變是不同步的——當(dāng)調(diào)用目標(biāo)被另一個(gè)線程修改,現(xiàn)在的線程不一定能同步到更新的值。為了強(qiáng)制同步,可以使用MutableCallSite::syncAll。連接到它的inDy指令每次調(diào)用都會(huì)調(diào)用它當(dāng)前的方法句柄目標(biāo)。
VolatileCallSite類似MutableCallSite,其指向的目標(biāo)可以修改。它的行為類似volatile字段,另一個(gè)線程修改指向目標(biāo)會(huì)立刻反應(yīng)到現(xiàn)在的線程,因此不需要syncAll之類的方法保持同步。volatile會(huì)造成不可避免的性能損失,所以如果不涉及線程問題最好用MutableCallSite。
下面演示了常量動(dòng)態(tài)調(diào)用點(diǎn)的使用方法(此處不涉及inDy):
三. 引導(dǎo)方法(BootStrap Method,簡(jiǎn)稱BSM)
在Java類執(zhí)行中,少不了“動(dòng)態(tài)”的東西。這些動(dòng)態(tài)的東西分為兩類:一種是動(dòng)態(tài)計(jì)算調(diào)用點(diǎn),一種是動(dòng)態(tài)計(jì)算常量。引導(dǎo)方法就是為了它們產(chǎn)生的。
動(dòng)態(tài)計(jì)算常量,由ConstantDynamic表示。它們?cè)贘VM使用它們之前被解析,解析時(shí)調(diào)用的就是它內(nèi)部的引導(dǎo)方法和它們內(nèi)置的引導(dǎo)方法參數(shù)。
動(dòng)態(tài)計(jì)算調(diào)用點(diǎn),也就是inDy的實(shí)現(xiàn)。inDy的目標(biāo)在第一次調(diào)用它之前解析調(diào)用獲得CallSite。
引導(dǎo)方法的聲明有一定規(guī)則,和它們的使用方式有關(guān):
如果引導(dǎo)方法用于動(dòng)態(tài)計(jì)算常量,則引導(dǎo)方法的前三個(gè)參數(shù)分別是MethodHandles.Lookup、String、Class對(duì)象,分別代表了調(diào)用方、名稱和常量類型,后面的參數(shù)是其他靜態(tài)參數(shù),返回值需要與Class對(duì)象代表的類型保持一致(或者寫為Object,只需要運(yùn)行時(shí)返回值可以被強(qiáng)制轉(zhuǎn)換到指定類型就可以)。
如果引導(dǎo)方法用于動(dòng)態(tài)計(jì)算調(diào)用點(diǎn),則引導(dǎo)方法的前三個(gè)參數(shù)分別是MethodHandles.Lookup、String、MethodType對(duì)象,分別代表調(diào)用方、名稱和調(diào)用點(diǎn)方法類型,后面的參數(shù)是其他靜態(tài)參數(shù),它的返回值要求是CallSite(通常是ConstantCallSite,當(dāng)然寫成Object也可以,只要保證能被強(qiáng)制類型轉(zhuǎn)換成CallSite就不報(bào)錯(cuò))
下面是一些正確的用于動(dòng)態(tài)計(jì)算調(diào)用點(diǎn)的引導(dǎo)方法聲明:
注意:靜態(tài)參數(shù)允許了動(dòng)態(tài)計(jì)算常量傳入。
四. invokedynamic字節(jié)碼
經(jīng)過前面一系列的鋪墊,終于我們要講inDy該怎么寫入了。
寫入inDy字節(jié)碼需要使用MethodVisitor的方法,visitInvokeDynamicInsn:
它的四個(gè)參數(shù)分別是名稱、方法描述符、引導(dǎo)函數(shù)的句柄和傳入引導(dǎo)方法的靜態(tài)參數(shù)。名稱和描述符都分別對(duì)應(yīng)了引導(dǎo)方法的參數(shù):name(第二個(gè)參數(shù))、type(第三個(gè)參數(shù))。
這里面的Handle句柄不等于MethodHandle方法句柄,但是它們也是緊密相關(guān)的,它的定義如下:
可以看到這里的參數(shù)和visitMethodInsn的參數(shù)基本一樣。第一個(gè)參數(shù)是調(diào)用標(biāo)簽,分為9個(gè),它們與方法句柄差不多:
H_GETFIELD,對(duì)應(yīng)findGetter,字節(jié)碼getfield,要求isInterface是false
H_GETSTATIC,對(duì)應(yīng)findStaticGetter,字節(jié)碼getstatic,要求isInterface是false
H_PUTFIELD,對(duì)應(yīng)findSetter,字節(jié)碼putfield,要求isInterface是false
H_PUTSTATIC,對(duì)應(yīng)findStaticSetter,字節(jié)碼putstatic,要求isInterface是false
H_INVOKEVIRTUAL,對(duì)應(yīng)findVirtual,字節(jié)碼invokevirtual
H_INVOKESTATIC,對(duì)應(yīng)findStatic,字節(jié)碼invokestatic
H_INVOKESPECIAL,對(duì)應(yīng)findSpecial,字節(jié)碼invokespecial
H_NEWINVOKESPECIAL,對(duì)應(yīng)findConstuctor,字節(jié)碼invokespecial
H_INVOKEINTERFACE,對(duì)應(yīng)findVirtual,字節(jié)碼invokeinterface,isInterface是true
下面是個(gè)例子,將Arrays::binarySearch(Object[], int, int, Object) -> int用Handle表述:
那么inDy對(duì)操作棧做了什么?這就和它的第二個(gè)參數(shù),descriptor有關(guān)系了。
之前說過,BSM會(huì)傳入一個(gè)MethodType,而這個(gè)MethodType是用于描述返回動(dòng)態(tài)調(diào)用點(diǎn)目標(biāo)句柄的。又由于descriptor在字節(jié)碼中最終會(huì)解釋成為MethodType,所以能得出一個(gè)結(jié)論:descriptor決定了BSM返回CallSite內(nèi)部方法句柄的類型。
而inDy在JVM的操作正是通過CallSite獲取dynamicInvoker進(jìn)行調(diào)用——也就是說,inDy相當(dāng)于間接調(diào)用了一個(gè)類型為descriptor的方法。這樣我們就不難理解inDy對(duì)操作棧干了什么:彈出descriptor指定的一部分參數(shù)并壓回規(guī)定的返回值。
JVM調(diào)用BSM的邏輯可以在java.lang.invoke.BootstrapMethodInvoker找到。
使用inDy字節(jié)碼還需要一步操作:你需要讓你的類訪問MethodHandles.Lookup,因此你需要在類聲明時(shí)加入一個(gè)visitInnerClassInsn(其實(shí)不加也不會(huì)報(bào)錯(cuò),但是最好加上):
下面,是Java中invokedynamic的用法詳解:
五. lambda表達(dá)式
匿名函數(shù)表達(dá)式,簡(jiǎn)稱lambda表達(dá)式,它在Java 8被加入。它簡(jiǎn)化了一部分的匿名類,讓代碼更加簡(jiǎn)潔。
為了展示它的用法和字節(jié)碼表示,我們先定義一個(gè)接口和一個(gè)方法:
接著,我們使用這個(gè)方法:
這時(shí),后面的“() -> "hello"”被解析成了一個(gè)StringSupplier的實(shí)現(xiàn)類對(duì)象。但是,在字節(jié)碼中無法自動(dòng)去生成一個(gè)這樣的類用于適配它。于是,javac在此處寫入了inDy字節(jié)碼要求動(dòng)態(tài)生成。
動(dòng)態(tài)生成lambda調(diào)用點(diǎn)的引導(dǎo)方法位于java.lang.invoke.LambdaMetafactory:
通常情況下,javac生成的lambda都是通過第一個(gè)BSM的,這6個(gè)參數(shù)的意義分別是:
caller,由JVM提供的查找對(duì)象,lambda會(huì)使用這個(gè)進(jìn)行動(dòng)態(tài)類創(chuàng)建
interfaceMethodName,lambda實(shí)現(xiàn)接口內(nèi)部需要實(shí)現(xiàn)的方法名稱
factoryType,要求BSM返回CallSite內(nèi)部指向方法句柄的方法類型
interfaceMethodType,lambda實(shí)現(xiàn)接口內(nèi)需要實(shí)現(xiàn)方法的類型
implementation,實(shí)現(xiàn)lambda內(nèi)部代碼功能的方法句柄
dynamicMethodType,實(shí)現(xiàn)lambda內(nèi)部代碼功能方法的類型,和interfaceMethodType相同或者是它的更具體的類型
可以看到,為了提供lambda的功能,javac會(huì)讓inDy字節(jié)碼連接到另一個(gè)方法上去。這種方法不需要我們自己寫,它是編譯時(shí)自動(dòng)生成的,名稱是“l(fā)ambda$方法名$序號(hào)”。上例中,javac動(dòng)態(tài)生成的lambda方法如下:
這些方法都帶有private和synthetic的訪問標(biāo)志,是否擁有static訪問標(biāo)志取決于lambda在的方法是否靜態(tài)和是否使用this對(duì)象。
接下來,我們使用這個(gè)方法連接到LambdaMetafactory:
metafactory通過這些參數(shù)可以動(dòng)態(tài)創(chuàng)建一個(gè)類實(shí)現(xiàn)指定的接口獲得實(shí)現(xiàn)接口的對(duì)象。具體而言,它通過asm庫(kù)(java內(nèi)置了asm庫(kù))在現(xiàn)在的類中動(dòng)態(tài)的生成了內(nèi)部類,類的名稱是“$Lambda$序號(hào)”(但是在getClass()獲取時(shí)名稱不是這個(gè),因?yàn)檫@個(gè)類被“隱藏”定義,會(huì)帶上另一個(gè)編號(hào))。對(duì)于這個(gè)例子,生成的內(nèi)部類像下面這樣:
對(duì)于這種lambda表達(dá)式,生成的類對(duì)象永遠(yuǎn)不變,所以JVM對(duì)此進(jìn)行優(yōu)化——這種lambda只會(huì)生成一個(gè)實(shí)例,返回的CallSite其實(shí)只是返回一個(gè)常量(詳情可見InnerClassLambdaMetafactory)。
說回到字節(jié)碼的寫入。之前說過visitInvokeDynamicInsn和BSM的參數(shù)一一對(duì)應(yīng),所以我們可以這樣寫入:
除了這種lambda外,還有另一種lambda:它們需要局部變量傳入內(nèi)部。這些局部變量有要求——它們無法被修改,或者叫“等效終態(tài)”。下面是一個(gè)例子:
由于傳入了局部變量,lambda的實(shí)現(xiàn)方法就需要多加一個(gè)參數(shù)用于傳遞這個(gè)變量。下面是javac生成的lambda代理實(shí)現(xiàn)方法:
但是這個(gè)str要怎么透過inDy字節(jié)碼進(jìn)行傳入?JVM為了解決這個(gè)問題,在動(dòng)態(tài)生成的委托類上做了一些操作:讓傳入的變量先用構(gòu)造函數(shù)存儲(chǔ)在字段里,在調(diào)用時(shí)取出字段值:
但是,這種lambda的CallSite不能返回一個(gè)常量——因?yàn)槲覀儾荒鼙WC局部變量是同一個(gè)值!因此,這個(gè)CallSite內(nèi)部指向了動(dòng)態(tài)生成內(nèi)部類的構(gòu)造函數(shù)。
接下來,我們用字節(jié)碼寫入一下:
六. 方法引用
當(dāng)lambda內(nèi)只有一行方法調(diào)用時(shí),在特定條件下可以簡(jiǎn)寫為方法引用。它分為不同的類型:
1. 靜態(tài)調(diào)用
當(dāng)方法引用指向一個(gè)類中的靜態(tài)方法時(shí),就是靜態(tài)調(diào)用,類似于:
它的實(shí)現(xiàn)類似于lambda,但是不同的是,javac編譯時(shí)不會(huì)生成一個(gè)新的方法用于lambda定位,而是選擇直接指向這個(gè)方法:
2. 對(duì)象調(diào)用
當(dāng)方法引用的目標(biāo)不是靜態(tài)的,它就需要使用一個(gè)對(duì)象用于方法的調(diào)用,下面是個(gè)例子:
這類似于將局部變量傳入了lambda內(nèi)部,因此這里的inDy字節(jié)碼是這樣寫的:
對(duì)象調(diào)用的對(duì)象沒有特殊要求,只需要能獲得這個(gè)局部變量就可以。方法引用的目標(biāo)可以是實(shí)例方法,也可以是抽象方法(區(qū)別在于Handle的標(biāo)簽)。
由于JVM不能保證傳入的局部變量是非空的(例外就是上面的情況:直接新建對(duì)象),所以在傳入lambda之前,JVM會(huì)進(jìn)行requireNonNull進(jìn)行檢查。也就是說,下面這兩種方式等價(jià):
上面的方法引用版本的代碼可以寫為:
除了這兩種方式外,我們還能使用超類的實(shí)例方法,現(xiàn)在假設(shè)Test繼承于SuperTest,有一個(gè)superTest方法,這時(shí)javac不會(huì)直接引用超類方法,而是生成lambda實(shí)現(xiàn)方法在內(nèi)部調(diào)用invokespecial。下面是使用例:
javac生成的lambda實(shí)現(xiàn)方法是這樣的:
接下來的代碼省略(因?yàn)楹蜕厦嬉粯樱?/p>
最后,還有一種對(duì)象調(diào)用:lambda內(nèi)部傳入了一個(gè)對(duì)象,我們可以通過這個(gè)對(duì)象進(jìn)行調(diào)用。這個(gè)調(diào)用方式和靜態(tài)調(diào)用差不多,只不過Handle的標(biāo)簽是H_INVOKEVIRTUAL,這個(gè)也不舉例了。
3. 構(gòu)造函數(shù)調(diào)用
方法引用允許傳遞構(gòu)造函數(shù),下面使用了String的無參構(gòu)造函數(shù)傳入test內(nèi)部:
這時(shí),傳入的方法句柄是構(gòu)造函數(shù),對(duì)應(yīng)了Handle中的H_NEWINVOKESPECIAL:
4. 數(shù)組構(gòu)造調(diào)用
除了普通的構(gòu)造函數(shù),數(shù)組也可以通過方法引用創(chuàng)建。它只需要一個(gè)int作為參數(shù),因此它實(shí)現(xiàn)的方法必須只有一個(gè)int形參:
這種方法引用也不是直接指向構(gòu)造函數(shù)的,還是javac生成lambda實(shí)現(xiàn)方法并引用的:
到此,所有方法引用的寫入方式就都介紹完了。
七. 字符串連接
在學(xué)習(xí)Java的時(shí)候,我們就知道Java的String允許用+進(jìn)行連接。但是,Java沒有符號(hào)重載,那么字符串是怎么打破這個(gè)限制的呢?答案就是javac編譯時(shí)做了一些“操作”。
接下來,我們使用這個(gè)例子:
在Java 8,字符串的連接被自動(dòng)識(shí)別為StringBuilder的鏈?zhǔn)秸{(diào)用,那么上面的這句話在javac編譯之后就變成了這樣:
字節(jié)碼寫入如下:
但是這種方式有兩個(gè)缺點(diǎn):一是會(huì)生成大量的字節(jié)碼片段,使類文件膨脹;二是這種調(diào)用每次都會(huì)生成StringBuilder對(duì)象,性能會(huì)損失一部分。
所以,從Java 9開始,字符串連接使用inDy字節(jié)碼動(dòng)態(tài)調(diào)用。它使用的引導(dǎo)方法位于StringConcatFactory。
makeConcat是makeConcatWithConstants的簡(jiǎn)化版本,如果沒有常量,就用第一個(gè)方法,但是javac編譯時(shí)通常使用第二個(gè)方法,所以我們對(duì)它進(jìn)行講解。
首先說說方法參數(shù)的意義:
lookup,由JVM提供的查找對(duì)象
name,名稱,和最后的連接效果沒有任何關(guān)系,只要不是null都能傳入。程序?qū)懭氤S谩癿akeConcatWithConstants”
concatType,生成CallSite的簽名,返回值需要是String,參數(shù)列表要和字符串中的變量的數(shù)量、類型和位置保持一致
recipe,用于連接字符串的模板,只有兩種字符:\u0001代表了這里應(yīng)該寫入變量,\u0002代表這里應(yīng)該寫入常量。\u0001的數(shù)量、位置需要和變量保持一致;\u0002的數(shù)量、位置要與常量保持一致
constants,字符串中的常量部分,數(shù)量和\u0002一致,可以不是String。
它的原理比lambda要簡(jiǎn)單——它是動(dòng)態(tài)生成了一個(gè)MethodHandle存儲(chǔ)到CallSite中,因此在執(zhí)行一次BSM之后它就成為了常量。
現(xiàn)在我們用它寫入字節(jié)碼:
但是你可能有一個(gè)疑問:如果我字符串里面本身有\(zhòng)u0001或者\(yùn)u0002不就出錯(cuò)了嗎?JVM考慮了這個(gè)情況,它的解決方案是——提取這一段字符串為常量放到后面。例如下面這個(gè)字符串:
它的寫入是:
八. 模式匹配
在Java 17,模式匹配進(jìn)行了預(yù)覽。下面就是它的使用例:
模式匹配之間必須使用break,否則會(huì)被提示為非法。
使用增強(qiáng)型switch可以寫成下面形式:
它的寫入和其他switch不同,它使用了inDy用于獲得序號(hào),再用這個(gè)序號(hào)進(jìn)行l(wèi)ookupswitch。
它使用的引導(dǎo)方法位于java.lang.runtime.SwitchBootstraps:
它的參數(shù)意義如下:
lookup,由JVM提供的查找對(duì)象
invocationName,名稱,和最后的效果沒有任何關(guān)系,只要不是null都能傳入。程序?qū)懭氤S谩皌ypeSwitch”
invocationType,要求第一個(gè)參數(shù)非基本類型、第二個(gè)參數(shù)是int、返回值是int的方法類型,也就是說它強(qiáng)制要求傳入一個(gè)對(duì)象和一個(gè)int。對(duì)象用于檢查模式,int用于確定lookupswitch的起始位置(通常是0)。返回值是從第二個(gè)參數(shù)開始的連續(xù)數(shù)列中的一個(gè)值。
labels,模式匹配目標(biāo)??梢允荂lass、Integer或String對(duì)象,但是實(shí)際上編譯時(shí)只使用了Class對(duì)象。它規(guī)定了返回的CallSite的內(nèi)容:如果輸入的對(duì)象是這個(gè)Class的對(duì)象,返回對(duì)應(yīng)的位置。如果輸入的對(duì)象是null,返回-1。如果輸入的對(duì)象不存在匹配項(xiàng),返回labels數(shù)組的長(zhǎng)度。
通過這個(gè)引導(dǎo)方法,上面的代碼可以變?yōu)椋?/p>
使用字節(jié)碼寫入:
* 通過SwitchBootstrap可以看出switch以后可能將盡可能使用invokedynamic:typeSwitch支持String輸入,也許之后會(huì)將String的switch語句修改為這種實(shí)現(xiàn);現(xiàn)在的類內(nèi)部還有一個(gè)enumSwitch但是javac并不能編譯出這個(gè)引導(dǎo)方法。在下個(gè)版本也許會(huì)進(jìn)一步增加細(xì)節(jié)。
九. 自定義引導(dǎo)方法
注意:自定義一個(gè)引導(dǎo)方法可能導(dǎo)致你的程序不穩(wěn)定、出現(xiàn)奇奇怪怪的問題、編譯變得極度麻煩。如果不是特殊用途(比如說真正的讓一個(gè)反編譯器完全失效)不要用這個(gè)!
根據(jù)上面引導(dǎo)方法的定義和inDy的實(shí)現(xiàn),我們自己也能創(chuàng)造出一個(gè)引導(dǎo)方法——只需要滿足要求就好。下面就是一個(gè)簡(jiǎn)單的引導(dǎo)方法:
接著,我們?cè)谖覀兊姆椒ɡ锩嬗米止?jié)碼指向它:
接下來就是執(zhí)行,輸出結(jié)果是:
這樣,我們就成功讓字節(jié)碼指向我們自定義的引導(dǎo)方法。
全部的代碼:https://paste.ubuntu.com/p/d82FNcP6jS/
十. 動(dòng)態(tài)常量(Constant Dynamic)
之前在BSM那里簡(jiǎn)單提到了動(dòng)態(tài)計(jì)算常量,這是JEP 309(Java 11)引入的,在這里我們?cè)龠M(jìn)一步深入講解。
首先,它的BSM定義和動(dòng)態(tài)調(diào)用點(diǎn)的BSM定義方式不同,詳情可以看上面。在寫入ASM時(shí),它使用的是visitLdcInsn,和普通常量一樣。
創(chuàng)建一個(gè)動(dòng)態(tài)常量使用ConstantDynamic,它的構(gòu)造函數(shù)如下:
可以看到它和visitInvokeDynamicInsn差不多,唯一的區(qū)別是:descriptor是類描述符而不是方法描述符。因此,所有動(dòng)態(tài)常量的BSM都不允許傳入變量。
有關(guān)于動(dòng)態(tài)常量的BSM都存儲(chǔ)到了一個(gè)類中:java.lang.invoke.ConstantBootstraps。
使用動(dòng)態(tài)計(jì)算常量可以使用其他動(dòng)態(tài)計(jì)算常量作為靜態(tài)參數(shù),這時(shí)JVM會(huì)倒序一個(gè)個(gè)計(jì)算創(chuàng)建常量。
下面是個(gè)例子:
字節(jié)碼寫入如下:
每個(gè)ConstantDynamic都可以復(fù)用——你可以使用一個(gè)對(duì)象傳入到不同的LDC里面去。這些對(duì)象最終和普通常量一樣存儲(chǔ)到常量池內(nèi)部。

這篇專欄就這些了,只講了一個(gè)字節(jié)碼,但是內(nèi)容很多。加上以前的一共191個(gè)。
有錯(cuò)誤可以在評(píng)論區(qū)指出。
這篇專欄也同步到我的博客上,可以去看看)