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.
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.
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.
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.
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.
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.
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, invokedynamicermö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:
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:
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:
meth.invoke(args)
. Wieinvokedynamic
passt das zusammenmeth.invoke
?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.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 warinvokedynamic
). Höchstwahrscheinlich werden sich viele funktionale Programmiersprachen zusätzlich zu JVM dafür entscheiden, diese anstelle von anon-inneren Klassen zu kompilieren.Vor einiger Zeit hat C # eine coole Funktion hinzugefügt, die dynamische Syntax in C #
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
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
invokedynamic
ihrer Programme zu nutzen.quelle
invokedynamic
war 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.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.
quelle
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
invokedynamic
Opcode 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:
invokestatic
statische Methodeninvokeinterface
aufrufen, Schnittstellenmethodeninvokespecial
aufrufen, Konstruktorensuper()
oder private Methodeninvokevirtual
aufrufen 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
invokedynamic
Anweisung 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:Dann gibt die Bootstrap-Methode eine Instanz von zurück
java.lang.invoke.CallSite
. DiesCallSite
enthä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:
Der Bytecode für dieses Beispiel wäre ungefähr so:
In der Bootstrap-Methodentabelle :
Daher wird die Bootstrap- Methode für Datensätze aufgerufen,
bootstrap
die sich in derjava.lang.runtime.ObjectMethods
Klasse befindet. Wie Sie sehen können, erwartet diese Bootstrap-Methode die folgenden Parameter:MethodHandles.Lookup
Darstellung des Suchkontexts (DerLjava/lang/invoke/MethodHandles$Lookup
Teil).toString
,equals
,hashCode
, etc.) die Bootstrap Link wird. Wenn der Wert beispielsweise "ist"toString
, gibt bootstrap einConstantCallSite
(aCallSite
, das sich nie ändert) zurück, das auf die tatsächlichetoString
Implementierung für diesen bestimmten Datensatz verweist .TypeDescriptor
für die Methode (Ljava/lang/invoke/TypeDescriptor
Teil).Class<?>
das den Datensatzklassentyp darstellt. Es istClass<Range>
in diesem Fall.min;max
. H.MethodHandle
pro Komponente. Auf diese Weise kann die Bootstrap-MethodeMethodHandle
basierend auf den Komponenten für diese bestimmte Methodenimplementierung eine erstellen .Die
invokedynamic
Anweisung übergibt alle diese Argumente an die Bootstrap-Methode. Die Bootstrap-Methode gibt wiederum eine Instanz von zurückConstantCallSite
. Dies enthältConstantCallSite
einen Verweis auf die angeforderte Methodenimplementierung, ztoString
.Warum Indy?
Im Gegensatz zu den Reflection-APIs ist die
java.lang.invoke
API 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
invokedynamic
Ansatz 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
invokedynamic
kann 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:
LambdaMetafactory
StringConcatFactory
quelle