最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Java ASM詳解:MethodVisitor與Opcode(五)invokedynamic、方法句柄、lambda

2021-12-02 16:01 作者:Nickid2018  | 我要投稿

前四篇專欄已經(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ū)指出。

這篇專欄也同步到我的博客上,可以去看看)

Java ASM詳解:MethodVisitor與Opcode(五)invokedynamic、方法句柄、lambda的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
清镇市| 志丹县| 旺苍县| 洱源县| 商城县| 湖南省| 英吉沙县| 措勤县| 阆中市| 霍城县| 金山区| 南通市| 都昌县| 安平县| 河池市| 馆陶县| 平乐县| 泾阳县| 米易县| 那坡县| 六盘水市| 美姑县| 开鲁县| 定兴县| 新疆| 兰坪| 安龙县| 凤翔县| 新津县| 富民县| 安仁县| 徐闻县| 淅川县| 宁化县| 闽侯县| 安平县| 咸宁市| 灵川县| 鹤山市| 瑞安市| 清水县|