invokedynamic คืออะไรและฉันจะใช้ได้อย่างไร


159

ฉันคอยฟังเกี่ยวกับคุณสมบัติเจ๋ง ๆ ใหม่ ๆ ที่กำลังเพิ่มเข้าไปใน JVM และหนึ่งในฟีเจอร์เจ๋ง ๆ เหล่านั้นก็คือการเรียกใช้ ฉันต้องการจะรู้ว่ามันคืออะไรและมันทำให้การเขียนโปรแกรมไตร่ตรองใน Java ง่ายขึ้นหรือดีขึ้นอย่างไร

คำตอบ:


165

มันเป็นคำสั่ง JVM ใหม่ที่อนุญาตให้คอมไพเลอร์สร้างโค้ดที่เรียกใช้เมธอดที่มีข้อมูลจำเพาะที่หลวมกว่าที่เป็นไปได้ก่อนหน้านี้ - ถ้าคุณรู้ว่า "การพิมพ์เป็ด " คืออะไร มีไม่มากนักที่โปรแกรมเมอร์ Java สามารถทำได้ หากคุณเป็นผู้สร้างเครื่องมือคุณสามารถใช้มันเพื่อสร้างภาษาที่ยืดหยุ่นและมีประสิทธิภาพมากขึ้นโดยใช้ภาษา JVM นี่คือโพสต์บล็อกหวานจริง ๆ ที่ให้รายละเอียดมากมาย


3
ในวันต่อวันโปรแกรม Java meth.invoke(args)มันไม่ได้เป็นเรื่องแปลกที่จะเห็นภาพสะท้อนถูกใช้ในการเรียกใช้วิธีการแบบไดนามิกด้วย ดังนั้นจะinvokedynamicพอดีกับได้meth.invokeอย่างไร
David K.

1
โพสต์บล็อกที่ฉันพูดถึงพูดถึงMethodHandleซึ่งเป็นสิ่งเดียวกัน แต่มีความยืดหยุ่นมากขึ้น แต่พลังที่แท้จริงในทั้งหมดนี้ไม่ได้เพิ่มเติมในภาษาจาวา แต่ในความสามารถของ JVM เองในการสนับสนุนภาษาอื่นที่มีความไดนามิกมากขึ้น
Ernest Friedman-Hill


1
ดูเหมือนว่า Java 8 แปลบางส่วนของ lambdas ใช้invokedynamicซึ่งจะทำให้มัน performant (เมื่อเทียบกับห่อไว้ในที่ไม่ระบุชื่อชั้นในระดับที่เกือบจะเป็นทางเลือกเดียวก่อนที่จะแนะนำinvokedynamic) ส่วนใหญ่อาจเป็นภาษาการเขียนโปรแกรมที่ใช้งานได้จำนวนมากที่ด้านบนของ JVM จะเลือกที่จะรวบรวมสิ่งนี้แทนการเรียนแบบอินไลน์ภายใน
Nader Ghanbari

2
เพียงคำเตือนเล็กน้อยบล็อกโพสต์จากปี 2008 นั้นล้าสมัยอย่างสิ้นหวังและไม่ได้สะท้อนสถานะการเปิดตัวจริง (2011)
Holger

9

เมื่อเวลาผ่านไป C # ได้เพิ่มฟีเจอร์เจ๋ง ๆ , ซินแทกซ์แบบไดนามิกภายใน C #

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

คิดว่ามันเป็นไวยากรณ์น้ำตาลสำหรับการโทรวิธีการไตร่ตรอง มันสามารถมีแอพพลิเคชั่นที่น่าสนใจมาก ดูhttp://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

Neal Gafter ผู้รับผิดชอบประเภทไดนามิกของ C # เพิ่งเสียจาก SUN ไปยัง MS ดังนั้นจึงไม่มีเหตุผลที่จะคิดว่าสิ่งเดียวกันได้ถูกกล่าวถึงในอาทิตย์

ฉันจำได้ไม่นานหลังจากนั้นเพื่อน Java บางคนประกาศสิ่งที่คล้ายกัน

InvokeDynamic duck = obj;
duck.quack(); 

น่าเสียดายที่ฟีเจอร์นี้ไม่มีที่ไหนใน Java 7 ที่ผิดหวังมาก สำหรับโปรแกรมเมอร์ Java พวกเขาไม่มีวิธีที่ง่ายในการใช้ประโยชน์จากinvokedynamicในโปรแกรมของพวกเขา


41
invokedynamicไม่เคยตั้งใจจะใช้สำหรับโปรแกรมเมอร์ Java IMO มันไม่สอดคล้องกับปรัชญา Java เลย ถูกเพิ่มเป็นคุณลักษณะ JVM สำหรับภาษาที่ไม่ใช่ Java
Mark Peters

5
@ Mark ไม่เคยตั้งใจใคร มันไม่เหมือนมีโครงสร้างอำนาจที่ชัดเจนในดาราภาษา Java หรือมี "เจตนา" รวมที่กำหนดไว้อย่างดี สำหรับปรัชญาภาษา - ค่อนข้างเป็นไปได้ดูคำอธิบายของ Neal Gafter (ผู้ทรยศ!): infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
เถียงไม่ได้

3
@mark peters: invokedynamic มีไว้สำหรับโปรแกรมเมอร์ Java เท่านั้นไม่สามารถเข้าถึงได้โดยตรง มันเป็นพื้นฐานสำหรับการปิดของ Java 8
M Platvoet

2
@ โต้แย้ง: ไม่ได้ตั้งใจโดยผู้สนับสนุน JSR กำลังบอกว่าชื่อของ JSR คือ "การสนับสนุนภาษาที่พิมพ์แบบไดนามิกบนแพลตฟอร์ม Java" Java ไม่ใช่ภาษาที่พิมพ์แบบไดนามิก
Mark Peters

5
@Matvovoet: ฉันยังไม่ได้รับการปรับปรุงให้ทันสมัยอยู่เสมอ แต่มันก็ไม่ได้เป็นข้อกำหนดที่แน่นอนสำหรับการปิด อีกทางเลือกหนึ่งที่พวกเขาพูดถึงก็คือการปิดซินแท็กซ์น้ำตาลสำหรับคลาสภายในที่ไม่ระบุชื่อซึ่งสามารถทำได้โดยไม่ต้องเปลี่ยนข้อมูลจำเพาะของ VM แต่ประเด็นของฉันคือ JSR ไม่เคยตั้งใจที่จะนำการพิมพ์แบบไดนามิกมาสู่ภาษา Java ซึ่งมีความชัดเจนมากถ้าคุณอ่าน JSR
Mark Peters

4

มีแนวคิดสองประการที่ต้องทำความเข้าใจก่อนดำเนินการต่อไปเพื่อเรียกใช้ซินนา

1. Static กับ Dynamin Typing

คงที่ - การตรวจสอบประเภท preforms ณ เวลารวบรวม (เช่น Java)

แบบไดนามิก - การตรวจสอบประเภท preforms ที่รันไทม์ (เช่น JavaScript)

การตรวจสอบประเภทเป็นกระบวนการในการตรวจสอบว่าโปรแกรมนั้นปลอดภัยสำหรับประเภทนี้คือการตรวจสอบข้อมูลที่พิมพ์สำหรับตัวแปรคลาสและอินสแตนซ์พารามิเตอร์วิธีการค่าส่งคืนและตัวแปรอื่น ๆ เช่น Java รู้เกี่ยวกับ int, String, .. ณ เวลารวบรวมในขณะที่ชนิดของวัตถุใน JavaScript สามารถกำหนดได้ในขณะใช้งานจริงเท่านั้น

2. พิมพ์แข็งแรงและอ่อนแอ

Strong - ระบุข้อ จำกัด เกี่ยวกับประเภทของค่าที่กำหนดให้กับการทำงาน (เช่น Java)

Weak - แปลงอาร์กิวเมนต์ (casts) ของการดำเนินการหากอาร์กิวเมนต์เหล่านั้นมีประเภทที่เข้ากันไม่ได้ (เช่น Visual Basic)

รู้ว่า Java เป็นแบบคงที่และอ่อนพิมพ์คุณจะใช้ภาษาที่พิมพ์แบบไดนามิกและแข็งแกร่งใน JVM ได้อย่างไร

invokedynamic ใช้ระบบรันไทม์ที่สามารถเลือกการใช้งานที่เหมาะสมที่สุดของวิธีการหรือฟังก์ชั่น - หลังจากโปรแกรมได้รับการรวบรวม

ตัวอย่าง: การมี (a + b) และไม่รู้อะไรเกี่ยวกับตัวแปร a, b ในเวลารวบรวม, invokedynamic แม็พการดำเนินการนี้กับวิธีที่เหมาะสมที่สุดใน Java ตอนรันไทม์ เช่นถ้าปรากฎ a, b คือ Strings ให้เรียกเมธอด (String a, String b) ถ้าปรากฎว่า a, b คือ ints แล้วเรียกเมธอด (int a, int b)

invokedynamic ถูกนำมาใช้กับ Java 7


4

เป็นส่วนหนึ่งของJava Recordsของฉันบทความฉันฉันพูดชัดแจ้งเกี่ยวกับแรงจูงใจเบื้องหลัง Inoke Dynamic เรามาเริ่มด้วยนิยามคร่าวๆของอินดี้กันดีกว่า

แนะนำอินดี้

Invoke Dynamic (เรียกอีกอย่างว่าIndy ) เป็นส่วนหนึ่งของJSR 292 ที่ต้องการเพิ่มประสิทธิภาพการสนับสนุน JVM สำหรับ Dynamic Type Languages หลังจากเปิดตัวครั้งแรกใน Java 7, invokedynamicopcode พร้อมกับมันjava.lang.invokeกระเป๋าเดินทางนั้นถูกใช้อย่างกว้างขวางโดยภาษาที่ใช้ JVM แบบไดนามิกเช่น JRuby

แม้ว่าอินดี้ออกแบบมาเป็นพิเศษเพื่อปรับปรุงการรองรับภาษาแบบไดนามิก แต่ก็มีให้มากกว่านั้น ตามความเป็นจริงแล้วมันเหมาะที่จะใช้ในทุกที่ที่นักออกแบบภาษาต้องการรูปแบบใด ๆ ของไดนามิกจากการแสดงกายกรรมแบบไดนามิกไปจนถึงกลยุทธ์แบบไดนามิก!

ตัวอย่างเช่นJava 8 Lambda Expressions นั้นถูกนำมาใช้จริงinvokedynamicแม้ว่า Java เป็นภาษาที่พิมพ์แบบคงที่!

Bytecode กำหนดโดยผู้ใช้

สำหรับค่อนข้างบางเวลา JVM ไม่สนับสนุนสี่ประเภทวิธีการอุทธรณ์: invokestaticเรียกวิธีคงinvokeinterfaceเรียกวิธีอินเตอร์เฟซinvokespecialที่จะเรียกก่อสร้าง, super()หรือวิธีการภาคเอกชนและinvokevirtualเรียกเมธอดอินสแตนซ์

แม้จะมีความแตกต่างกัน แต่ประเภทการเรียกใช้เหล่านี้มีลักษณะทั่วไปเพียงอย่างเดียวนั่นคือเราไม่สามารถยกระดับให้กับตรรกะของเราเองได้ ในทางตรงกันข้ามinvokedynamic ช่วยให้เราสามารถ Bootstrap กระบวนการร้องขอในวิธีที่เราต้องการ จากนั้น JVM จะดูแลการโทร Bootstrapped Method โดยตรง

อินดี้ทำงานอย่างไร

ครั้งแรกที่ JVM เห็นinvokedynamicการเรียนการสอนที่เรียกว่าวิธีการแบบคงที่พิเศษที่เรียกว่าวิธี Bootstrap วิธีบู๊ตสแตรปเป็นส่วนหนึ่งของรหัส Java ที่เราเขียนขึ้นเพื่อเตรียมตรรกะจริงที่จะเรียกใช้:

ป้อนคำอธิบายรูปภาพที่นี่

java.lang.invoke.CallSiteแล้ววิธีบูตส่งกลับตัวอย่างของ นี่CallSiteถือเป็นการอ้างอิงถึงวิธีการที่เกิดขึ้นจริงเช่นMethodHandleถืออ้างอิงถึงวิธีการที่เกิดขึ้นจริงคือ

นับจากนี้ไปทุกครั้งที่ JVM เห็นinvokedynamicคำสั่งนี้อีกครั้งจะข้ามเส้นทางที่ช้าและเรียกใช้ไฟล์ปฏิบัติการโดยตรง JVM จะข้ามเส้นทางช้าอย่างต่อเนื่องเว้นแต่มีการเปลี่ยนแปลงบางอย่าง

ตัวอย่าง: Java 14 Records

Java 14 Recordsกำลังจัดเตรียมไวยากรณ์ที่กระชับไว้เป็นอย่างดีเพื่อประกาศคลาสที่ควรจะเป็นผู้ถือข้อมูลที่โง่

พิจารณาบันทึกง่าย ๆ นี้:

public record Range(int min, int max) {}

โค้ดไบต์สำหรับตัวอย่างนี้จะเป็นดังนี้:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

ในตารางวิธี Bootstrap :

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

ดังนั้นวิธีการบูตสแตรปสำหรับเร็กคอร์ดจึงถูกเรียกbootstrapซึ่งอยู่ในjava.lang.runtime.ObjectMethodsคลาส อย่างที่คุณเห็นวิธีการบู๊ตสแตรปนี้ต้องการพารามิเตอร์ต่อไปนี้:

  • อินสแตนซ์ของการMethodHandles.Lookupแทนบริบทการค้นหา ( Ljava/lang/invoke/MethodHandles$Lookupส่วนหนึ่ง)
  • ชื่อวิธี (เช่นtoString, equals, hashCodeฯลฯ ) บูตจะไปเชื่อมโยง ตัวอย่างเช่นเมื่อค่าคือtoStringbootstrap จะส่งกลับ a ConstantCallSite( CallSiteที่ไม่เคยเปลี่ยนแปลง) ที่ชี้ไปที่toStringการใช้งานจริงสำหรับบันทึกนี้โดยเฉพาะ
  • TypeDescriptorสำหรับวิธีการ ( Ljava/lang/invoke/TypeDescriptor บางส่วน)
  • ประเภทโทเค็นคือClass<?>ตัวแทนประเภทชั้นเรียนบันทึก มันเป็น Class<Range>ในกรณีนี้
  • min;maxกึ่งลำไส้ใหญ่แยกรายการสินค้าทั้งหมดของชื่อองค์ประกอบคือ
  • หนึ่งรายการMethodHandleต่อองค์ประกอบ วิธีนี้วิธีบู๊ตสแตรปสามารถสร้างMethodHandleส่วนประกอบบนพื้นฐานสำหรับการใช้วิธีนี้โดยเฉพาะ

invokedynamicการเรียนการสอนผ่านทุกข้อโต้แย้งเหล่านั้นไปยังวิธีการบูต ConstantCallSiteวิธีการบูตในทางกลับกันกลับตัวอย่างของ นี้มีการถือครองการอ้างอิงถึงการดำเนินการตามวิธีการร้องขอเช่นConstantCallSitetoString

ทำไมต้องอินดี้

ตรงข้ามกับ Reflection APIs java.lang.invoke API ค่อนข้างมีประสิทธิภาพเนื่องจาก JVM สามารถมองเห็นการเรียกใช้ทั้งหมดได้อย่างสมบูรณ์ ดังนั้น JVM อาจใช้การเพิ่มประสิทธิภาพทุกประเภทตราบใดที่เราหลีกเลี่ยงเส้นทางช้ามากที่สุด!

นอกเหนือจากข้อโต้แย้งด้านประสิทธิภาพแล้วinvokedynamicวิธีการนี้ยังมีความน่าเชื่อถือและเปราะน้อยกว่าเนื่องจากความเรียบง่ายความเรียบง่าย

นอกจากนี้โค้ดไบต์ที่สร้างขึ้นสำหรับ Java Records นั้นไม่ขึ้นอยู่กับจำนวนของคุณสมบัติ ดังนั้นน้อย bytecode และเวลาเริ่มต้นเร็วขึ้น

สุดท้ายสมมติว่า Java เวอร์ชันใหม่มีการใช้วิธีบูตสแตรปแบบใหม่และมีประสิทธิภาพยิ่งขึ้น ด้วยinvokedynamicแอปของเราสามารถใช้ประโยชน์จากการปรับปรุงนี้โดยไม่ต้องรวบรวมใหม่ วิธีนี้เรามีความเข้ากันได้แบบส่งต่อไบนารีบางอย่าง นั่นคือกลยุทธ์ที่เรากำลังพูดถึง!

ตัวอย่างอื่น ๆ

นอกจาก Java Records แล้วInvoke dynamicยังถูกใช้เพื่อสร้างฟีเจอร์เช่น:

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.