小度拆卸_拆卸invokedynamic
小度拆卸
許多Java開發(fā)人員認為JDK的第七版有些令人失望。 從表面上看,僅少數(shù)語言和庫擴展使它成為了發(fā)行版,即Project Coin和NIO2 。 但在幕后,該平臺的第七個版本對JVM類型系統(tǒng)進行了最大的擴展,這是其最初發(fā)行后引入的。 添加invokedynamic指令不僅為在Java 8中實現(xiàn)lambda表達式奠定了基礎(chǔ),而且還是將動態(tài)語言轉(zhuǎn)換為Java字節(jié)碼格式的規(guī)則改變者。
雖然invokedynamic指令是用于在Java虛擬機上執(zhí)行語言的實現(xiàn)細節(jié),但了解該指令的功能可以真正洞悉執(zhí)行Java程序的內(nèi)部工作原理。 本文提供了有關(guān)invokedynamic指令解決什么問題以及如何解決它的初學者的見解。
方法句柄
方法句柄通常被描述為Java反射API的改進版本,但這并不是它們所代表的意思。 盡管方法句柄確實表示方法,構(gòu)造函數(shù)或字段,但它們并不旨在描述這些類成員的屬性。 例如,不可能直接從方法句柄中提取元數(shù)據(jù),例如所表示方法的修飾符或注釋值。 雖然方法句柄允許引用方法的調(diào)用,但它們的主要目的是與invokedynamic調(diào)用站點一起使用。 為了更好地理解方法句柄,將它們視為反射API的不完美替代是一個合理的起點。
方法句柄無法實例化。 而是使用指定的查找對象創(chuàng)建方法句柄。 這些對象本身是使用MethodHandles類提供的工廠方法創(chuàng)建的。 每當調(diào)用工廠時,它都會首先創(chuàng)建一個安全上下文,以確保所生成的查找對象只能定位對調(diào)用工廠方法的類也可見的方法。 然后可以按以下方式創(chuàng)建查找對象:
class Example {void doSomething() {MethodHandles.Lookup lookup = MethodHandles.lookup();} }如前所述,以上查找對象只能用于定位對Example類也可見的方法。 例如,不可能查找另一個類的私有方法。 這是使用反射API的第一個主要區(qū)別,反射API可以像定位其他任何方法一樣定位外部類的私有方法,并且在將此類方法標記為可訪問之后甚至可以調(diào)用這些方法。 因此,方法句柄對它們的創(chuàng)建上下文很敏感,這是與反射API的第一個主要區(qū)別。
除此之外,方法句柄通過描述特定類型的方法而不是僅代表任何方法,比反射API更具體。 在Java程序中,方法的類型是該方法的返回類型及其參數(shù)類型的組合。 例如,以下Counter類的only方法返回一個int,它表示唯一的String型參數(shù)的字符數(shù):
class Counter {static int count(String name) {return name.length();} }可以使用另一個工廠來創(chuàng)建此方法類型的表示形式。 該工廠位于MethodType類中,該類還表示創(chuàng)建的方法類型的實例。 使用該工廠,可以通過移交方法的返回類型及其捆綁為數(shù)組的參數(shù)類型來創(chuàng)建Counter::count的方法類型:
MethodType methodType = MethodType.methodType(int.class, new Class<?>[] {String.class});描述上述方法的類型時,將方法聲明為靜態(tài)是很重要的。 編譯Java方法時,非靜態(tài)Java方法的表示方式類似于靜態(tài)方法,但帶有一個表示此偽變量的附加隱式參數(shù)。 因此,在為非靜態(tài)方法創(chuàng)建MethodType時,需要傳遞代表該方法的聲明類型的附加參數(shù)。 因此,對于上述Counter::count方法的非靜態(tài)版本,方法類型將變?yōu)橐韵骂愋?#xff1a;
MethodType.methodType(int.class, Example.class, new Class<?>[] {String.class});通過使用之前創(chuàng)建的查找對象以及上面的方法類型,現(xiàn)在可以找到代表Counter::count方法的方法句柄,如以下代碼所示:
MethodType methodType = MethodType.methodType(int.class, new Class<?>[] {String.class}); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle methodHandle = lookup.findStatic(Counter.class, "count", methodType); int count = methodHandle.invokeExact("foo"); assertThat(count, is(3));乍一看,使用方法句柄似乎是使用反射API的過于復雜的版本。 但是,請記住,使用句柄直接調(diào)用方法并不是其使用的主要目的。
上面的示例代碼和通過反射API調(diào)用方法的主要區(qū)別僅在研究Java編譯器將兩次調(diào)用轉(zhuǎn)換為Java字節(jié)碼的方式的區(qū)別時才顯示出來。 當Java程序調(diào)用方法時,該方法由其名稱,其(非通用)參數(shù)類型以及其返回類型唯一標識。 因此,可以重載Java中的方法。 即使Java編程語言不允許這樣做,但JVM在理論上確實允許通過其返回類型來重載方法。
遵循此原理,將反射方法調(diào)用作為Method :: invoke方法的公共方法調(diào)用執(zhí)行。 此方法由其兩個參數(shù)(類型為Object和Object [])標識。 除此之外,該方法還通過其對象返回類型來標識。 由于具有此簽名,因此必須始終將此方法的所有參數(shù)裝箱并包含在數(shù)組中。 同樣,如果返回值是原始值,則需要將其裝箱;如果該方法無效,則返回null。
方法句柄是該規(guī)則的例外。 不是通過引用MethodHandle::invokeExact簽名的方法來調(diào)用方法句柄,該簽名將Object[]作為其單個參數(shù)并返回Object ,而是使用所謂的多態(tài)簽名來調(diào)用方法句柄。 Java編譯器根據(jù)調(diào)用現(xiàn)場的實際參數(shù)類型和期望的返回類型來創(chuàng)建多態(tài)簽名。 例如,當使用
int count = methodHandle.invokeExact("foo");Java編譯器將轉(zhuǎn)換此調(diào)用,就像將invokeExact方法定義為接受String類型的單個單個參數(shù)并返回int類型一樣。 顯然,這種方法不存在,并且對于(幾乎)任何其他方法,這將在運行時導致鏈接錯誤。 對于方法句柄,Java虛擬機確實將此簽名識別為多態(tài)的,并且將方法句柄的調(diào)用視為該句柄所引用的Counter::count方法直接插入到調(diào)用站點中。 因此,可以調(diào)用該方法而無需將原始值裝箱或返回類型的開銷,也無需將參數(shù)值放在數(shù)組內(nèi)。
同時,在使用invokeExact調(diào)用時,向Java虛擬機保證方法句柄在運行時始終引用與多態(tài)簽名兼容的方法。 對于該示例,JVM期望所引用的方法實際上接受String作為其唯一參數(shù),并且它返回原始int 。 如果未滿足此約束,則執(zhí)行將導致運行時錯誤。 但是,任何其他接受單個String并返回原始int的方法都可以成功地填充到方法句柄的調(diào)用站點中,以替換Counter::count 。
相反,即使代碼成功編譯,在以下三個調(diào)用中使用Counter::count方法句柄也會導致運行時錯誤:
int count1 = methodHandle.invokeExact((Object) "foo"); int count2 = (Integer) methodHandle.invokeExact("foo"); methodHandle.invokeExact("foo");第一條語句導致錯誤,因為傳遞給句柄的參數(shù)過于籠統(tǒng)。 盡管JVM期望將String作為方法的參數(shù),但Java編譯器建議該參數(shù)為Object類型。 重要的是要理解,Java編譯器將強制轉(zhuǎn)換作為創(chuàng)建不同的多態(tài)簽名的提示,該簽名將Object類型作為單個參數(shù)類型,而JVM在運行時期望使用String 。 請注意,此限制也適用于處理過于具體的參數(shù),例如,將參數(shù)強制轉(zhuǎn)換為Integer ,該方法的句柄需要使用Number類型作為其參數(shù)。 在第二條語句中,Java編譯器向運行時建議,句柄的方法將返回Integer包裝器類型,而不是原始int 。 而且,在第三條語句中根本不建議返回類型,Java編譯器將調(diào)用隱式轉(zhuǎn)換為void方法調(diào)用。 因此, invokeExact確實意味著精確。
這種限制有時可能太苛刻。 出于這個原因,方法句柄不需要進行確切的調(diào)用,還允許在應用了諸如類型轉(zhuǎn)換和拳擊等轉(zhuǎn)換的情況下更為寬容的調(diào)用。 可以通過使用MethodHandle::invoke方法來應用MethodHandle::invoke 。 使用此方法,Java編譯器仍會創(chuàng)建一個多態(tài)簽名。 但這一次,Java虛擬機確實在運行時測試了實際參數(shù)和返回類型的兼容性,并在適當時通過應用裝箱或轉(zhuǎn)換來轉(zhuǎn)換它們。 顯然,這些轉(zhuǎn)換有時會增加運行時的開銷。
字段,方法和構(gòu)造函數(shù):作為統(tǒng)一接口處理
除了反射API的Method實例之外,方法句柄可以同樣引用字段或構(gòu)造函數(shù)。 因此,可以將MethodHandle類型的名稱視為太窄。 實際上,在運行時通過方法句柄引用哪個類成員并不重要,只要它的MethodType (具有誤導性名稱的另一種類型)與在關(guān)聯(lián)的調(diào)用站點傳遞的參數(shù)匹配即可。
使用MethodHandles.Lookup對象的適當工廠,可以查找一個字段以表示一個getter或setter。 在此上下文中使用getter或setter并不表示調(diào)用遵循Java Bean規(guī)范的實際方法。 而是,基于字段的方法句柄直接從字段讀取或?qū)懭胱侄?#xff0c;但通過調(diào)用方法句柄以方法調(diào)用的形式出現(xiàn)。 通過經(jīng)由方法句柄表示此類字段訪問,可以互換使用字段訪問或方法調(diào)用。
以此類交換為例,采用以下類:
class Bean {String value;void print(String x) {System.out.println(x);} }給定此Bean類,可以使用以下方法句柄將字符串寫到value字段或使用與參數(shù)相同的字符串調(diào)用print方法:
MethodHandle fieldHandle = lookup.findSetter(Bean.class, "value", String.class); MethodType methodType = MethodType.methodType(void.class, new Class<?>[] {String.class}); MethodHandle methodHandle = lookup.findVirtual(Bean.class, "print", methodType);只要在返回void同時將方法句柄調(diào)用站點與String一起傳遞給Bean的實例,則兩個方法句柄可以互換使用,如下所示:
anyHandle.invokeExact((Bean) mybean, (String) myString);與字段和方法類似,可以定位和調(diào)用構(gòu)造函數(shù)。 此外,只要創(chuàng)建查找工廠的類可以訪問此超級方法,則它不僅可以直接調(diào)用方法,甚至可以調(diào)用超級方法。 相反,在依賴反射API時根本不可能調(diào)用超級方法。 如果需要,甚至可以從句柄返回恒定值。
性能指標
方法句柄通常被描述為比Java反射API更高性能。 至少對于最新版本的HotSpot虛擬機而言,這不是事實。 證明這一點的最簡單方法是編寫適當?shù)幕鶞?。 再說一次,為Java程序編寫基準并在執(zhí)行時進行優(yōu)化并不是一件容易的事。 編寫基準的事實上的標準已成為使用JMH的工具,JMH是OpenJDK旗下的工具。 完整的基準可以在我的GitHub個人資料中找到要點。 本文僅涵蓋該基準測試的最重要方面。
從基準來看,很明顯反射已經(jīng)非常有效地實現(xiàn)了。 現(xiàn)代JVM知道一個稱為膨脹的概念,其中經(jīng)常調(diào)用的反射方法調(diào)用被運行時生成的Java字節(jié)代碼替換。 剩下的是將拳擊用于傳遞參數(shù)和接收返回值的開銷。 有時可以通過JVM的即時編譯器消除這些拳擊,但這并不總是可能的。 因此,如果方法調(diào)用涉及大量原始值,則使用方法句柄可能比使用反射API更有效。 但是,這確實要求在編譯時已經(jīng)知道確切的方法簽名,以便可以創(chuàng)建適當?shù)亩鄳B(tài)簽名。 對于大多數(shù)反射API用例,由于在編譯時不知道被調(diào)用方法的類型,因此無法提供此保證。 在這種情況下,使用方法句柄不會帶來任何性能上的好處,因此不應替換它。
創(chuàng)建一個invokedynamic呼叫站點
通常,僅當Java編譯器需要將lambda表達式轉(zhuǎn)換為字節(jié)碼時,才會創(chuàng)建invokedynamic調(diào)用站點。 值得一提的是,lambda表達式可以在沒有完全調(diào)用動態(tài)調(diào)用站點的情況下實現(xiàn),例如通過將它們轉(zhuǎn)換為匿名內(nèi)部類。 與建議的方法的主要區(qū)別是,使用invokedynamic會延遲創(chuàng)建與運行時類似的類。 我們將在下一部分中研究類的創(chuàng)建。 但是,現(xiàn)在請記住,invokedynamic與類創(chuàng)建沒有任何關(guān)系,它僅允許將如何調(diào)度方法的決定延遲到運行時。
為了更好地理解invokedynamic調(diào)用站點,它有助于顯式創(chuàng)建此類調(diào)用站點,以便單獨查看機制。 為此,下面的示例利用了我的代碼生成框架Byte Buddy ,該框架提供了對invokedynamic調(diào)用站點的顯式字節(jié)代碼生成,而無需任何字節(jié)代碼格式的知識。
任何invokedynamic調(diào)用站點最終都會產(chǎn)生一個MethodHandle,該MethodHandle引用要調(diào)用的方法。 但是,不是手動調(diào)用此方法句柄,而是由Java運行時決定。 因為方法句柄已成為Java虛擬機的已知概念,所以這些調(diào)用的優(yōu)化類似于常見方法調(diào)用。 任何這樣的方法句柄都是從所謂的引導程序方法接收的,而引導程序方法僅是滿足特定簽名的普通Java方法。 有關(guān)引導方法的簡單示例,請查看以下代碼:
class Bootstrapper {public static CallSite bootstrap(Object... args) throws Throwable {MethodType methodType = MethodType.methodType(int.class, new Class<?>[] {String.class})MethodHandles.Lookup lookup = MethodHandles.lookup();MethodHandle methodHandle = lookup.findStatic(Counter.class, "count", methodType);return new ConstantCallSite(methodHandle);} }目前,我們不太關(guān)心該方法的參數(shù)。 相反,請注意,該方法是靜態(tài)的,實際上是必需的。 在Java字節(jié)代碼中,invokedynamic調(diào)用站點引用引導程序方法的完整簽名,但不引用可能具有狀態(tài)和生命周期的特定對象。 調(diào)用invokedynamic調(diào)用站點后,控制流將移交給引用的引導方法,該方法現(xiàn)在負責標識方法句柄。 從bootstrap方法返回此方法句柄后,它將由Java運行時調(diào)用。
從上面的示例可以明顯看出, MethodHandle不是直接從引導方法返回的。 而是將句柄包裝在CallSite對象的內(nèi)部。 每當調(diào)用引導方法時,invokedynamic調(diào)用站點便會永久綁定到從該方法返回的CallSite對象。 因此,對于任何呼叫站點僅一次調(diào)用引導程序方法。 由于有了這個中間的CallSite對象,因此可以在以后交換引用的MethodHandle 。 為此,Java類庫已經(jīng)提供了CallSite不同實現(xiàn)。 在上面的示例代碼中,我們已經(jīng)看到了ConstantCallSite 。 顧名思義, ConstantCallSite始終引用相同的方法句柄,而不會在以后進行交換。 但是,也可以選擇使用MutableCallSite ,它允許在以后的某個時間點更改引用的MethodHandle ,或者甚至有可能實現(xiàn)自定義的CallSite類。
通過上述引導程序方法和Byte Buddy,我們現(xiàn)在可以實現(xiàn)自定義invokedynamic指令。 為此,Byte Buddy提供了InvokeDynamic工具,該工具接受bootstrap方法作為其唯一的強制參數(shù)。 然后將這樣的儀器輸入到Byte Buddy。 假設(shè)下面的類:
abstract class Example {abstract int method(); }我們可以使用Byte Buddy來對Example進行子類化,以覆蓋method 。 然后,我們將實現(xiàn)此方法以包含單個invokedynamic調(diào)用站點。 無需任何進一步配置,Byte Buddy就會創(chuàng)建一個類似于覆蓋方法的方法類型的多態(tài)簽名。 請記住,對于非靜態(tài)方法,此引用將作為第一個隱式參數(shù)處理。 假定我們要綁定將String作為單個參數(shù)的Counter::count方法,則無法將此句柄綁定到與方法類型不匹配的Example::method 。 因此,我們需要創(chuàng)建一個不帶隱式參數(shù)但使用String代替的其他調(diào)用站點。 這可以通過使用Byte Buddy的域特定語言來實現(xiàn):
Instrumentation invokeDynamic = InvokeDynamic.bootstrap(Bootstrapper.class.getDeclaredMethod(“bootstrap”, Object[].class)).withoutImplicitArguments().withValue("foo");有了此工具,我們最終可以擴展Example類和重寫方法,以實現(xiàn)invokedynamic調(diào)用站點,如以下代碼片段所示:
Example example = new ByteBuddy().subclass(Example.class).method(named(“method”)).intercept(invokeDynamic).make().load(Example.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded().newInstance(); int result = example.method(); assertThat(result, is(3));從上面的斷言可以明顯看出, "foo"字符串的字符已正確計數(shù)。 通過在代碼中設(shè)置適當?shù)臄帱c,還可以驗證是否調(diào)用了bootstrap方法,并且控制流進一步到達了Counter::count方法。
到目前為止,使用invokedynamic調(diào)用站點并沒有帶來太多好處。 上面的bootstrap方法將始終綁定Counter::count ,因此只有在invokedynamic調(diào)用站點確實想要將String轉(zhuǎn)換為int時才可以產(chǎn)生有效結(jié)果。 顯然,由于引導方法從invokedynamic調(diào)用站點接收到的參數(shù),因此引導方法可以更加靈活。 任何引導方法都至少接收三個參數(shù):
作為第一個參數(shù),bootstrap方法接收一個MethodHandles.Lookup對象。 該對象的安全上下文是包含觸發(fā)自舉的invokedynamic調(diào)用站點的類的安全上下文。 如前所述,這意味著可以使用此查找實例將定義類的私有方法綁定到invokedynamic調(diào)用站點。
第二個參數(shù)是一個表示方法名稱的String 。 該字符串用作提示,指示從調(diào)用站點應將哪種方法綁定到它。 嚴格來說,不需要此參數(shù),因為將方法與其他名稱綁定是完全合法的。 如果沒有另外指定,Byte Buddy僅將覆蓋方法的名稱用作此參數(shù)。
最后,將預期返回的方法句柄的MethodType用作第三個參數(shù)。 對于上面的示例,我們明確指定希望將String作為單個參數(shù)。 同時,Byte Buddy通過查看重寫的方法得出我們需要一個int作為返回值,因為我們再次沒有指定任何顯式的返回類型。
引導程序方法的實現(xiàn)者應取決于引導程序方法的確切簽名,只要它至少可以接受這三個參數(shù)即可。 如果引導程序方法的最后一個參數(shù)表示Object數(shù)組,則該最后一個參數(shù)將被視為varargs,因此可以接受任何多余的參數(shù)。 這也是上述示例引導程序方法有效的原因。
此外,引導程序方法可以從invokedynamic調(diào)用站點接收多個參數(shù),只要這些參數(shù)可以存儲在類的常量池中即可。 對于任何Java類,常量池都存儲在類內(nèi)部使用的值,主要是數(shù)字或字符串值。 從今天起,此類常量可以是至少32位大小的原始值, String , Class , MethodHandl和MethodType 。 如果查找合適的方法句柄需要此類參數(shù)形式的附加信息,則可以使引導方法更靈活地使用。
Lambda表達式
每當Java編譯器將lambda表達式轉(zhuǎn)換為字節(jié)碼時,它都會將lambda的主體復制到定義該表達式的類內(nèi)部的私有方法中。 這些方法被命名為lambda$X$Y其中X是包含lambda表達式的方法的名稱,而Y是從零開始的序列號。 這種方法的參數(shù)是lambda表達式實現(xiàn)的功能接口的參數(shù)。 假定lambda表達式不使用非靜態(tài)字段或封閉類的方法,則該方法也定義為靜態(tài)的。
為了進行補償,lambda表達式本身被invokedynamic調(diào)用站點替代。 在調(diào)用時,此調(diào)用站點請求為功能接口的實例綁定工廠。 作為此工廠的參數(shù),調(diào)用站點提供了在表達式內(nèi)部使用的lambda表達式的封閉方法的任何值,并在需要時提供對封閉實例的引用。 作為返回類型,要求工廠提供功能接口的實例。
為了引導呼叫站點,當前任何invokedynamic指令都委托給Java類庫中包含的LambdaMetafactory類。 然后,該工廠負責創(chuàng)建一個實現(xiàn)功能接口的類,該類調(diào)用包含lambda主體的適當方法,該主體如前所述存儲在原始類中。 但是,將來這種引導過程可能會更改,這是使用invokedynamic來實現(xiàn)lambda表達式的主要優(yōu)點之一。 如果有一天,可以使用一種更適合的語言功能來實現(xiàn)lambda表達式,則可以簡單地替換掉當前的實現(xiàn)。
為了能夠創(chuàng)建實現(xiàn)功能接口的類,任何表示lambda表達式的調(diào)用站點都會為bootstrap方法提供其他參數(shù)。 對于強制性參數(shù),它已經(jīng)提供了功能接口方法的名稱。 而且,它提供了引導應該產(chǎn)生的工廠方法的MethodType 。 此外,為引導方法提供了另一個MethodType ,它描述了功能接口方法的簽名。 為此,它接收到一個MethodHandle該方法引用包含lambda的方法主體的方法。 最后,調(diào)用站點提供了功能接口方法的通用簽名的MethodType ,即在應用類型擦除之前在調(diào)用站點上方法的簽名。
調(diào)用時,bootstrap方法將查看這些參數(shù),并創(chuàng)建實現(xiàn)功能接口的類的適當實現(xiàn)。 此類是使用ASM庫創(chuàng)建的, ASM庫是一種低級字節(jié)代碼解析器和編寫器,它已成為直接Java字節(jié)代碼操作的事實上的標準。 bootstrap方法除了實現(xiàn)功能接口的方法外,還添加了適當?shù)臉?gòu)造函數(shù)和靜態(tài)工廠方法來創(chuàng)建類的實例。 此工廠方法后來綁定到invokedyanmic調(diào)用站點。 作為自變量,工廠將接收lambda方法的封閉實例的實例,以防其被訪問以及從封閉方法中讀取的任何值。
例如,請考慮以下lambda表達式:
class Foo {int i;void bar(int j) {Consumer consumer = k -> System.out.println(i + j + k);} }為了執(zhí)行,lambda表達式需要訪問Foo的封閉實例及其封閉方法的值j。 因此,上述類的已廢止版本看起來類似于以下內(nèi)容,其中invokedynamic指令由某些偽代碼表示:
class Foo {int i;void bar(int j) {Consumer consumer = <invokedynamic(this, j)>;}private /* non-static */ void lambda$foo$0(int j, int k) {System.out.println(this.i + j + k);} }為了能夠調(diào)用lambda$foo$0 ,將封閉的Foo實例和j變量都傳遞到由invokedyanmic指令綁定的工廠。 然后,該工廠接收其所需的變量,以創(chuàng)建所生成類的實例。 然后,此生成的類如下所示:
class Foo$$Lambda$0 implements Consumer {private final Foo _this;private final int j;private Foo$$Lambda$0(Foo _this, int j) {this._this = _this;this.j = j;}private static Consumer get$Lambda(Foo _this, int j) {return new Foo$$Lambda$0(_this, j);}public void accept(Object value) { // type erasure_this.lambda$foo$0(_this, j, (Integer) value);} }最終,生成的類的工廠方法通過一個由ConstantCallSite包含的方法句柄綁定到invokedynamic調(diào)用站點。 但是,如果lambda表達式是完全無狀態(tài)的,即它不需要訪問包含它的實例或方法,則LambdaMetafactory返回一個所謂的常量方法句柄,該句柄引用一個急切創(chuàng)建的生成類實例。 因此,此實例用作單例,以便每次到達lambda表達式的調(diào)用站點時使用。 顯然,此優(yōu)化決策會影響您的應用程序的內(nèi)存占用,并且在編寫lambda表達式時要牢記這一點。 同樣,沒有工廠方法被添加到無狀態(tài)lambda表達式的類中。
您可能已經(jīng)注意到,lambda表達式的方法主體包含在一個私有方法中,該方法現(xiàn)在從另一個類中調(diào)用。 通常,這將導致非法訪問錯誤。 為了克服此限制,使用所謂的匿名類加載來加載生成的類。 僅當通過傳遞字節(jié)數(shù)組顯式加載類時,才可以應用匿名類加載。 另外,通常無法在用戶代碼中應用匿名類加載,因為匿名類加載已隱藏在Java類庫的內(nèi)部類中。 當使用匿名類加載來加載類時,它會收到一個其繼承其完整安全上下文的宿主類。 這涉及方法和字段訪問權(quán)限以及保護域,因此也可以為簽名的jar文件生成lambda表達式。 使用此方法,可以認為lambda表達式比匿名內(nèi)部類更安全,因為從類外部永遠無法訪問私有方法。
內(nèi)幕:lambda表格
Lambda表單是虛擬機如何執(zhí)行MethodHandles的實現(xiàn)細節(jié)。 由于其名稱,lambda形式經(jīng)常與lambda表達式混淆。 取而代之的是,lambda表單受lambda演算的啟發(fā),并因此獲得了它們的名稱,而不是因為其在OpenJDK中實現(xiàn)lambda表達式的實際用法。
在OpenJDK 7的早期版本中,方法句柄可以兩種方式之一執(zhí)行。 方法句柄要么直接呈現(xiàn)為字節(jié)碼,要么使用Java運行時提供的顯式匯編代碼進行分派。 字節(jié)碼呈現(xiàn)已應用于在Java類的整個生命周期中被認為是完全恒定的任何方法句柄。 但是,如果JVM無法證明該屬性,則通過將其分配給提供的匯編代碼來執(zhí)行方法句柄。 不幸的是,由于Java的JIT編譯器無法優(yōu)化匯編代碼,因此導致了非恒定的方法句柄調(diào)用,從而“降低了性能”。 由于這也會影響延遲綁定的lambda表達式,因此,這顯然不是令人滿意的解決方案。
引入LambdaForm來解決此問題。 粗略地說,lambda形式表示字節(jié)碼指令,如前所述,可以由JIT編譯器進行優(yōu)化。 在OpenJDK中,今天,方法句柄通過LambdaForm表示MethodHandle的調(diào)用語義。 通過這種可優(yōu)化的中間表示,非恒定MethodHandle的使用變得更加MethodHandle 。 實際上,甚至有可能看到字節(jié)碼編譯的LambdaForm在LambdaForm 。 只需將斷點放置在bootstrap方法內(nèi)部或通過MethodHandle調(diào)用的方法內(nèi)部。 斷點LambdaForm可以在調(diào)用堆棧中找到經(jīng)過字節(jié)碼轉(zhuǎn)換的LambdaForm 。
為什么這對動態(tài)語言很重要
應該在Java虛擬機上執(zhí)行的任何語言都必須轉(zhuǎn)換為Java字節(jié)碼。 顧名思義,Java字節(jié)碼與Java編程語言非常接近。 這包括為任何值定義嚴格類型的要求,并且在引入invokedynamic之前,需要一個方法調(diào)用來指定用于調(diào)度方法的顯式目標類。 查看以下JavaScript代碼,但是在將方法轉(zhuǎn)換為字節(jié)碼時無法指定任何信息:
function (foo) {foo.bar(); }通過使用invokedynamic調(diào)用站點,可以將方法的調(diào)度程序的標識延遲到運行時,此外,在需要更正先前決策的情況下,可以重新綁定調(diào)用目標。 以前,使用具有所有性能缺陷的反射API是實現(xiàn)動態(tài)語言的唯一真正選擇。
因此,invokedynamic指令的真正受益者是動態(tài)編程語言。 添加指令是使字節(jié)碼格式與Java編程語言保持一致的第一步,這使JVM即使對于動態(tài)語言也成為強大的運行時。 而且,正如lambda表達式所證明的那樣,這種將重點放在將動態(tài)語言托管在JVM上的重點并沒有干擾Java語言的發(fā)展。 相反,Java編程語言是從這些努力中獲得的。
翻譯自: https://www.javacodegeeks.com/2015/04/dismantling-invokedynamic.html
小度拆卸
總結(jié)
以上是生活随笔為你收集整理的小度拆卸_拆卸invokedynamic的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 本地构建和自动化构建_构建自动化面板
- 下一篇: DOTA2电脑配置清单?