Invokedynamic:Java的秘密武器
在Java 7的發布版中包含了多項新的特性,這些特性乍看上去Java開發人員對它們的使用非常有限,在我們之前的文章中,曾經對其進行過介紹。
\\但是,其中有項特性對于實現Java 8中“頭版標題”類型的特性來說至關重要(如lambdas和默認方法)。在本文中,我們將會深入學習invokedynamic,并闡述它對于Java平臺以及像JRuby和Nashorn這樣的JVM語言來講為何如此重要。
\\invokedynamic最初的工作至少始于2007年,而第一次成功的動態調用發生在2008年8月26日。這比Oracle收購Sun還要早,按照大多數開發人員的標準,這個特性的研發已經持續了相當長的時間。
\\值得注意的是,從Java 1.0到現在,invokedynamic是第一個新加入的Java字節碼,它與已有的字節碼invokevirtual、invokestatic、invokeinterface和invokespecial組合在了一起。已有的這四個操作碼實現了Java開發人員所熟知的所有形式的方法分派(dispatch):
\\- invokevirtual——對實例方法的標準分派\\t
- invokestatic——用于分派靜態方法\\t
- invokeinterface——用于通過接口進行方法調用的分派\\t
- invokespecial——當需要進行非虛(也就是“精確”)分派時會用到\
有些開發人員可能會好奇平臺為何需要這四種操作碼,所以我們看一個簡單的樣例,這個樣例會用到不同的調用操作碼,以此來闡述它們之間的差異:
\\\public class InvokeExamples {\ public static void main(String[] args) {\ InvokeExamples sc = new InvokeExamples();\ sc.run();\ }\\ private void run() {\ List ls = new ArrayList();\ ls.add(\"Good Day\");\\ ArrayList als = new ArrayList();\ als.add(\"Dydh Da\");\ }\}\\我們可以使用javap反匯編從而得到它所產生的字節碼:
\\\javap -c InvokeExamples.class\\public class kathik.InvokeExamples {\ public kathik.InvokeExamples();\ Code:\ 0: aload_0\ 1: invokespecial #1 // Method java/lang/Object.\"\":()V\ 4: return\\ public static void main(java.lang.String[]);\ Code:\ 0: new #2 // class kathik/InvokeExamples\ 3: dup\ 4: invokespecial #3 // Method \"\":()V\ 7: astore_1\ 8: aload_1\ 9: invokespecial #4 // Method run:()V\ 12: return\\ private void run();\ Code:\ 0: new #5 // class java/util/ArrayList\ 3: dup\ 4: invokespecial #6 // Method java/util/ArrayList.\"\":()V\ 7: astore_1\ 8: aload_1\ 9: ldc #7 // String Good Day\ 11: invokeinterface #8, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z\ 16: pop\ 17: new #5 // class java/util/ArrayList\ 20: dup\ 21: invokespecial #6 // Method java/util/ArrayList.\"\":()V\ 24: astore_2\ 25: aload_2\ 26: ldc #9 // String Dydh Da\ 28: invokevirtual #10 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z\ 31: pop\ 32: return\}\\在這個示例中,展現了四個調用操作碼中的三個(剩下的一個也就是invokestatic,是一個非常簡單的擴展)。作為開始,我們可以看一下如下的兩個調用(在run方法的字節11和28):
\\ls.add(\"Good Day\")
\\和
\\als.add(\"Dydh Da\")
\\在Java源碼中它們看起來非常相似,但它們實際上卻代表兩種不同的字節碼。
\\對于javac來說,變量ls具有的靜態類型是List\u0026lt;String\u0026gt;,而List是一個接口。所以,在運行時方法表(通常稱為“vtable”)中,add()方法的精確位置還沒有在編譯時確定。因此,源碼編譯器會生成一個invokeinterface指令,將實際的方法查找推遲到運行期,也就是當ls的實際vtable能夠探查到并且add()方法的位置能夠找到的時候。
\\與之相反,對als.add(\"Dydh Da\")的調用是通過als來執行的,這里的靜態類型是類類型(class type)——ArrayList\u0026lt;String\u0026gt;。這意味著在vtable中,方法的位置在編譯期是可知的。因此,javac會針對這個精確的vtable條目生成一個invokevirtual指令。不過,最終的方法選擇依然是在運行期確定的,因為這里還有方法重寫(overriding)的可能性,但是vtable slot在編譯期就已經確定了。
\\除此之外,這個樣例還展現了invokespecial的兩個使用場景。這個操作碼用于在運行時確定如何分派的場景之中,具體來講,在這里沒有方法重寫的需求,另外這也不可能實現。樣例中所闡述的場景是private methods和super calls,這些方法在編譯期是可知的,并且無法進行重寫。
\\細心的讀者可能已經發現,對Java方法的所有調用都編譯成了四個操作碼中的某一個,那么問題就來了——invokedynamic是做什么的,它對于Java開發人員有什么用處呢?
\\這個特性的主要目標在于創建一個字節碼,用于處理新型的方法分派——它的本質是允許應用級別的代碼來確定執行哪一個方法調用,只有在調用要執行的時候,才會進行這種判斷。這樣的話,相對于Java平臺之前所提供的編程風格,允許語言和框架的編寫人員支持更加動態的編碼風格。
\\它的目的在于由用戶代碼通過方法句柄API(method handles API)在運行時確定如何分派,同時避免反射帶來的性能懲罰和安全問題。實際上,invokedynamic所宣稱的目標就是一旦該特性足夠成熟,它的速度要像常規的方法分派(invokevirtual)一樣快。
\\當Java 7發布的時候,JVM就已經支持執行新的字節碼了,但是不管提交什么樣的Java代碼,javac都不會產生包含invokedynamic的字節碼。這項特性用來支持JRuby和其他運行在JVM上的動態語言。
\\在Java 8中,這發生了變化,在實現lambda表達式和默認方法時,底層會生成和使用invokedynamic,它同時還會作為Nashorn的首選分派機制。但是,對于Java應用的開發人員來說,依然沒有直接的方式實現完全的動態方法處理(resolution)。也就是說,Java語言并沒有提供關鍵字或庫來創建通用的invokedynamic調用點(call site)。這意味著,盡管這種機制的功能非常強大,但它對于大多數的Java開發人員來說依然有些陌生。接下來,我們看一下如何在自己的代碼中使用這項技術。
\\方法句柄簡介
\\要讓invokedynamic正常運行,一個核心的概念就是方法句柄(method handle)。它代表了一個可以從invokedynamic調用點進行調用的方法。這里的基本理念就是每個invokedynamic指令都會與一個特定的方法關聯(也就是引導方法或BSM)。當解釋器(interpreter)遇到invokedynamic指令的時候,BSM會被調用。它會返回一個對象(包含了一個方法句柄),這個對象表明了調用點要實際執行哪個方法。
\\在一定程度上,這與反射有些類似,但是反射有它的局限性,這些局限性使它不適合與invokedynamic協作使用。Java 7 API中加入了java.lang.invoke.MethodHandle(及其子類),通過它們來代表invokedynamic指向的方法。為了實現操作的正確性,MethodHandle會得到JVM的一些特殊處理。
\\理解方法句柄的一種方式就是將其視為以安全、現代的方式來實現反射的核心功能,在這個過程會盡可能地保證類型的安全。invokedynamic需要方法句柄,另外它們也可以單獨使用。
\\方法類型
\\一個Java方法可以視為由四個基本內容所構成:
\\- 名稱\\t
- 簽名(包含返回類型)\\t
- 定義它的類\\t
- 實現方法的字節碼\
這意味著如果要引用某個方法,我們需要有一種有效的方式來表示方法簽名(而不是反射中強制使用的令人討厭的Class\u0026lt;?\u0026gt;[] hack方式)。
\\接下來我們采用另外的方式,方法句柄首先需要的一個構建塊就是表達方法簽名的方式,以便于查找。在Java 7引入的Method Handles API中,這個角色是由java.lang.invoke.MethodType類來完成的,它使用一個不可變的實例來代表簽名。要獲取MethodType,我們可以使用methodType()工廠方法。這是一個參數可變(variadic)的方法,以class對象作為參數。
\\第一個參數所使用的class對象,對應著簽名的返回類型;剩余參數中所使用的class對象,對應著簽名中方法參數的類型。例如:
\\\//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); \\現在我們就可以使用MethodType,再組合方法名稱以及定義方法的類來查找方法句柄。要實現這一點,我們需要調用靜態的MethodHandles.lookup()方法。這樣的話,會給我們一個“查找上下文(lookup context)”,這個上下文基于當前正在執行的方法(也就是調用lookup()的方法)的訪問權限。
\\查找上下文對象有一些以“find”開頭的方法,例如,findVirtual()、findConstructor()、findStatic()等。這些方法將會返回實際的方法句柄,需要注意的是,只有在創建查找上下文的方法能夠訪問(調用)被請求方法的情況下,才會返回句柄。這與反射不同,我們沒有辦法繞過訪問控制。換句話說,方法句柄中并沒有與setAccessible()對應的方法。例如:
\\\public MethodHandle getToStringMH() {\ MethodHandle mh = null;\ MethodType mt = MethodType.methodType(String.class);\ MethodHandles.Lookup lk = MethodHandles.lookup();\\ try {\ mh = lk.findVirtual(getClass(), \"toString\總結
以上是生活随笔為你收集整理的Invokedynamic:Java的秘密武器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux--网卡聚合简单脚本(bond
- 下一篇: 方鑫装饰风水