Was ist invokedynamic und wie verwende ich es?

159

Ich höre immer wieder von all den neuen coolen Funktionen, die der JVM hinzugefügt werden, und eine dieser coolen Funktionen ist invokedynamic. Ich würde gerne wissen, was es ist und wie es die reflektierende Programmierung in Java einfacher oder besser macht.

David K.
quelle

Antworten:

165

Es ist eine neue JVM - Instruktion , die ein Compiler Code generieren können , welche Methoden mit einer lockereren Spezifikation nennt als dies bisher möglich war - wenn Sie wissen , was „ Duck Typing “ ist, invokedynamic erlaubt grundsätzlich für Ente eingeben. Sie als Java-Programmierer können nicht zu viel damit anfangen. Wenn Sie jedoch ein Tool-Ersteller sind, können Sie damit flexiblere und effizientere JVM-basierte Sprachen erstellen. Hier ist ein wirklich süßer Blog-Beitrag, der viele Details enthält.

Ernest Friedman-Hill
quelle
3
In der täglichen Java-Programmierung ist es nicht ungewöhnlich, dass Reflection verwendet wird, um Methoden dynamisch mit aufzurufen meth.invoke(args). Wie invokedynamicpasst das zusammen meth.invoke?
David K.
1
Der Blog-Beitrag, den ich erwähne, spricht darüber MethodHandle, was wirklich das Gleiche ist, aber mit viel mehr Flexibilität. Die wahre Kraft in all dem liegt jedoch nicht in der Ergänzung der Java-Sprache, sondern in den Fähigkeiten der JVM selbst, andere Sprachen zu unterstützen, die an sich dynamischer sind.
Ernest Friedman-Hill
1
Es scheint, dass Java 8 einige der Lambdas mit übersetzt invokedynamic, wodurch es performant wird (im Vergleich dazu, sie in eine anonyme innere Klasse zu packen, die vor der Einführung fast die einzige Wahl war invokedynamic). Höchstwahrscheinlich werden sich viele funktionale Programmiersprachen zusätzlich zu JVM dafür entscheiden, diese anstelle von anon-inneren Klassen zu kompilieren.
Nader Ghanbari
2
Nur eine kleine Warnung: Dieser Blog-Beitrag aus dem Jahr 2008 ist hoffnungslos veraltet und spiegelt nicht den tatsächlichen Veröffentlichungsstatus (2011) wider.
Holger
9

Vor einiger Zeit hat C # eine coole Funktion hinzugefügt, die dynamische Syntax in C #

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

Stellen Sie sich das als Syntaxzucker für reflektierende Methodenaufrufe vor. Es kann sehr interessante Anwendungen haben. Siehe http://www.infoq.com/presentations/Static-Dynamic-Typing-Neal-Gafter

Neal Gafter, der für den dynamischen Typ von C # verantwortlich ist, ist gerade von SUN zu MS übergegangen. Es ist also nicht unangemessen zu glauben, dass die gleichen Dinge in SUN besprochen wurden.

Ich erinnere mich, dass bald darauf ein Java-Typ etwas Ähnliches angekündigt hatte

InvokeDynamic duck = obj;
duck.quack(); 

Leider ist die Funktion in Java 7 nicht zu finden. Sehr enttäuscht. Für Java-Programmierer gibt es keine einfache Möglichkeit, die Vorteile invokedynamicihrer Programme zu nutzen.

unwiderlegbar
quelle
41
invokedynamicwar nie für Java-Programmierer gedacht . IMO passt es überhaupt nicht zur Java-Philosophie. Es wurde als JVM-Funktion für Nicht-Java-Sprachen hinzugefügt.
Mark Peters
5
@Mark Nie von wem beabsichtigt? Es ist nicht so, dass es eine klare Machtstruktur in Prominenten der Java-Sprache gibt, oder es gibt eine klar definierte kollektive "Absicht". Was die Sprachphilosophie betrifft - es ist durchaus machbar, siehe Erklärung von Neal Gafter (Verräter!): Infoq.com/presentations/Static-Dynamic-Typing-Neal-Gafter
unwiderlegbar
3
@mark peters: invokedynamic ist eigentlich auch für Java-Programmierer gedacht, auf die nur nicht direkt zugegriffen werden kann. Es ist die Basis für die Schließung von Java 8.
M Platvoet
2
@irreputable: Nie von den JSR-Mitwirkenden beabsichtigt. Es ist bezeichnend, dass der Name des JSR "Unterstützung dynamisch typisierter Sprachen auf der Java-Plattform" lautet. Java ist keine dynamisch typisierte Sprache.
Mark Peters
5
@M Platvoet: Ich bin mit Schließungen nicht auf dem Laufenden geblieben, aber es wäre sicherlich keine absolute Voraussetzung für Schließungen. Eine andere Option, die sie diskutierten, war einfach, syntaktischen Zucker für anonyme innere Klassen zu schließen, was ohne eine Änderung der VM-Spezifikation möglich war. Mein Punkt war jedoch, dass das JSR niemals dazu gedacht war, die Java-Sprache dynamisch zu tippen. Das ist klar, wenn Sie das JSR lesen.
Mark Peters
4

Es sind zwei Konzepte zu verstehen, bevor Sie mit der Aufrufdynamik fortfahren.

1. Statische vs. Dynamin-Typisierung

Statisch - Vorformlings-Typprüfung zur Kompilierungszeit (z. B. Java)

Dynamisch - Preforms-Typprüfung zur Laufzeit (z. B. JavaScript)

Bei der Typprüfung wird überprüft, ob ein Programm typsicher ist. Dabei werden typisierte Informationen auf Klassen- und Instanzvariablen, Methodenparameter, Rückgabewerte und andere Variablen überprüft. Zum Beispiel kennt Java int, String, .. zur Kompilierungszeit, während der Typ eines Objekts in JavaScript nur zur Laufzeit bestimmt werden kann

2. Stark gegen schwach tippen

Stark - Gibt Einschränkungen für die Arten von Werten an, die für seine Operationen bereitgestellt werden (z. B. Java).

Schwach - konvertiert (Casts) Argumente einer Operation, wenn diese Argumente inkompatible Typen haben (z. B. Visual Basic)

Wie können Sie dynamisch und stark typisierte Sprachen in der JVM implementieren, wenn Sie wissen, dass Java statisch und schwach typisiert ist?

Die aufgerufene Dynamik implementiert ein Laufzeitsystem, das die am besten geeignete Implementierung einer Methode oder Funktion auswählen kann - nachdem das Programm kompiliert wurde.

Beispiel: Mit (a + b) und ohne Kenntnis der Variablen a, b zur Kompilierungszeit ordnet invokedynamic diese Operation zur Laufzeit der am besten geeigneten Methode in Java zu. Wenn sich beispielsweise herausstellt, dass a, b Strings sind, rufen Sie die Methode (String a, String b) auf. Wenn sich herausstellt, dass a, b Ints sind, rufen Sie die Methode (int a, int b) auf.

invokedynamic wurde mit Java 7 eingeführt.

Sabina Orazem
quelle
4

Im Rahmen meiner Java Records Artikels habe ich über die Motivation hinter Inoke Dynamic gesprochen. Beginnen wir mit einer groben Definition von Indy.

Wir stellen vor: Indy

Invoke Dynamic (auch als Indy bekannt ) war Teil von JSR 292 , um die JVM-Unterstützung für dynamische Typensprachen zu verbessern. Nach seiner ersten Veröffentlichung in Java 7 wird der invokedynamicOpcode zusammen mit seinemjava.lang.invoke Gepäck von dynamischen JVM-basierten Sprachen wie JRuby ziemlich häufig verwendet.

Indy wurde speziell entwickelt, um die Unterstützung dynamischer Sprachen zu verbessern, bietet aber noch viel mehr. Tatsächlich ist es überall dort einsetzbar, wo ein Sprachdesigner irgendeine Form von Dynamik benötigt, von dynamischer Akrobatik bis hin zu dynamischen Strategien!

Zum Beispiel werden die Java 8 Lambda-Ausdrücke tatsächlich mit implementiert invokedynamic, obwohl Java eine statisch typisierte Sprache ist!

Benutzerdefinierter Bytecode

Seit geraumer Zeit unterstützt JVM vier Methodenaufruftypen: invokestaticstatische Methoden invokeinterfaceaufrufen, Schnittstellenmethoden invokespecialaufrufen, Konstruktoren super()oder private Methoden invokevirtualaufrufen und Instanzmethoden aufrufen.

Trotz ihrer Unterschiede haben diese Aufrufarten ein gemeinsames Merkmal: Wir können sie nicht mit unserer eigenen Logik bereichern . Im Gegenteil, invokedynamic ermöglicht es uns, den Aufrufprozess nach Belieben zu booten. Dann kümmert sich die JVM darum, die Bootstrapped-Methode direkt aufzurufen.

Wie funktioniert Indy?

Wenn JVM zum ersten Mal eine invokedynamicAnweisung sieht , ruft sie eine spezielle statische Methode namens Bootstrap-Methode auf . Die Bootstrap-Methode ist ein Teil des Java-Codes, den wir geschrieben haben, um die eigentliche aufzurufende Logik vorzubereiten:

Geben Sie hier die Bildbeschreibung ein

Dann gibt die Bootstrap-Methode eine Instanz von zurück java.lang.invoke.CallSite. Dies CallSiteenthält einen Verweis auf die tatsächliche Methode, dhMethodHandle .

Von nun an invokedynamicüberspringt JVM jedes Mal, wenn diese Anweisung erneut angezeigt wird, den langsamen Pfad und ruft die zugrunde liegende ausführbare Datei direkt auf. Die JVM überspringt weiterhin den langsamen Pfad, sofern sich nichts ändert.

Beispiel: Java 14-Datensätze

Java 14 Records bietet eine nette kompakte Syntax, um Klassen zu deklarieren, die dumme Dateninhaber sein sollen.

In Anbetracht dieser einfachen Aufzeichnung:

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

Der Bytecode für dieses Beispiel wäre ungefähr so:

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

In der Bootstrap-Methodentabelle :

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

Daher wird die Bootstrap- Methode für Datensätze aufgerufen, bootstrapdie sich in der java.lang.runtime.ObjectMethodsKlasse befindet. Wie Sie sehen können, erwartet diese Bootstrap-Methode die folgenden Parameter:

  • Eine Instanz zur MethodHandles.LookupDarstellung des Suchkontexts (Der Ljava/lang/invoke/MethodHandles$LookupTeil).
  • Der Methodenname (dh toString, equals, hashCode, etc.) die Bootstrap Link wird. Wenn der Wert beispielsweise "ist" toString, gibt bootstrap ein ConstantCallSite(a CallSite, das sich nie ändert) zurück, das auf die tatsächliche toStringImplementierung für diesen bestimmten Datensatz verweist .
  • Die TypeDescriptorfür die Methode ( Ljava/lang/invoke/TypeDescriptor Teil).
  • Ein Typ-Token, Class<?>das den Datensatzklassentyp darstellt. Es ist Class<Range>in diesem Fall.
  • Eine durch Semikolon getrennte Liste aller Komponentennamen, d min;max. H.
  • Eine MethodHandlepro Komponente. Auf diese Weise kann die Bootstrap-Methode MethodHandlebasierend auf den Komponenten für diese bestimmte Methodenimplementierung eine erstellen .

Die invokedynamicAnweisung übergibt alle diese Argumente an die Bootstrap-Methode. Die Bootstrap-Methode gibt wiederum eine Instanz von zurück ConstantCallSite. Dies enthält ConstantCallSiteeinen Verweis auf die angeforderte Methodenimplementierung, z toString.

Warum Indy?

Im Gegensatz zu den Reflection-APIs ist die java.lang.invokeAPI sehr effizient, da die JVM alle Aufrufe vollständig durchsehen kann. Daher kann JVM alle Arten von Optimierungen anwenden, solange wir den langsamen Pfad so weit wie möglich vermeiden!

Zusätzlich zum Effizienzargument ist der invokedynamicAnsatz aufgrund seiner Einfachheit zuverlässiger und weniger spröde .

Darüber hinaus ist der generierte Bytecode für Java Records unabhängig von der Anzahl der Eigenschaften. So weniger Bytecode und schnellere Startzeit.

Nehmen wir zum Schluss an, eine neue Version von Java enthält eine neue und effizientere Implementierung der Bootstrap-Methode. Mit invokedynamickann unsere App diese Verbesserung ohne Neukompilierung nutzen. Auf diese Weise haben wir eine Art Forward Binary Compatibility . Das ist auch die dynamische Strategie, über die wir gesprochen haben!

Andere Beispiele

Zusätzlich zu Java Records wurde die Aufrufdynamik verwendet, um Funktionen wie Folgendes zu implementieren:

Ali Dehghani
quelle