Qu'est-ce qui est invokedynamic et comment l'utiliser?

159

Je continue d'entendre parler de toutes les nouvelles fonctionnalités intéressantes qui sont ajoutées à la JVM et l'une de ces fonctionnalités intéressantes est invokedynamic. J'aimerais savoir ce que c'est et comment cela rend-il la programmation réflexive en Java plus facile ou meilleure?

David K.
la source

Réponses:

165

C'est une nouvelle instruction JVM qui permet à un compilateur de générer du code qui appelle des méthodes avec une spécification plus lâche qu'auparavant - si vous savez ce qu'est le " typage canard ", invokedynamic permet essentiellement le typage canard. Il n'y a pas grand-chose que vous pouvez faire avec un programmeur Java; si vous êtes un créateur d'outils, cependant, vous pouvez l'utiliser pour créer des langages JVM plus flexibles et plus efficaces. Voici un article de blog vraiment sympa qui donne beaucoup de détails.

Ernest Friedman-Hill
la source
3
Dans la programmation Java quotidienne, il n'est pas rare de voir la réflexion utilisée pour invoquer des méthodes dynamiquement avec meth.invoke(args). Alors, comment ça va invokedynamicavec meth.invoke?
David K.
1
Le billet de blog dont je parle parle MethodHandle, qui est vraiment le même genre de chose mais avec beaucoup plus de flexibilité. Mais la vraie puissance dans tout cela ne vient pas des ajouts au langage Java, mais des capacités de la JVM elle-même à prendre en charge d'autres langages qui sont intrinsèquement plus dynamiques.
Ernest Friedman-Hill
1
Il semble que Java 8 traduit certains des lambdas en utilisant invokedynamicce qui le rend performant (par rapport à les envelopper dans une classe interne anonyme qui était presque le seul choix avant l'introduction invokedynamic). Très probablement, beaucoup de langages de programmation fonctionnels au-dessus de JVM choisiront de compiler vers cela au lieu de classes anon-internes.
Nader Ghanbari
2
Juste un petit avertissement, ce billet de blog de 2008 est désespérément obsolète et ne reflète pas l'état de sortie réel (2011).
Holger
9

Il y a quelque temps, C # a ajouté une fonctionnalité intéressante, une syntaxe dynamique dans C #

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

Considérez-le comme du sucre de syntaxe pour les appels de méthode réflexifs. Cela peut avoir des applications très intéressantes. voir http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

Neal Gafter, qui est responsable du type dynamique de C #, vient de passer de SUN à MS. Il n'est donc pas déraisonnable de penser que les mêmes choses ont été discutées à l'intérieur de SUN.

Je me souviens que peu de temps après, un mec Java a annoncé quelque chose de similaire

InvokeDynamic duck = obj;
duck.quack(); 

Malheureusement, la fonctionnalité est introuvable dans Java 7. Très déçu. Pour les programmeurs Java, ils n'ont pas de moyen facile d'en tirer parti invokedynamicdans leurs programmes.

irréprochable
la source
41
invokedynamicn'a jamais été conçu pour être utilisé par les programmeurs Java. OMI, cela ne correspond pas du tout à la philosophie Java. Il a été ajouté en tant que fonctionnalité JVM pour les langages non Java.
Mark Peters
5
@Mark Jamais voulu par qui? Ce n'est pas comme s'il y avait une structure de pouvoir claire chez les célébrités du langage Java, ou qu'il y avait une «intention» collective bien définie. Quant à la philosophie du langage - c'est tout à fait faisable, voir l'explication de Neal Gafter (traître!): Infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
irréputable
3
@mark peters: invokedynamic est en fait également destiné aux programmeurs java qui ne sont pas directement accessibles. C'est la base des fermetures de Java 8.
M Platvoet
2
@irreputable: jamais voulu par les contributeurs JSR. Il est révélateur que le nom du JSR est "Supporting Dynamically Typed Languages ​​on the Java Platform". Java n'est pas un langage typé dynamiquement.
Mark Peters
5
@M Platvoet: Je ne suis pas resté au courant des fermetures, mais ce ne serait certainement pas une exigence absolue pour les fermetures. Une autre option dont ils ont discuté était simplement de faire des fermetures syntaxiques pour les classes internes anonymes, ce qui pourrait être fait sans changement de spécification de VM. Mais mon point était que le JSR n'a jamais été destiné à apporter le typage dynamique au langage Java, cela est clair si vous lisez le JSR.
Mark Peters
4

Il y a deux concepts à comprendre avant de continuer à invokedynamic.

1. Typage statique ou dynamin

Statique - vérification du type des préformes au moment de la compilation (par exemple Java)

Dynamique - vérification du type de préformes au moment de l'exécution (par exemple JavaScript)

La vérification de type est un processus de vérification de la sécurité d'un programme, c'est-à-dire la vérification des informations typées pour les variables de classe et d'instance, les paramètres de méthode, les valeurs de retour et d'autres variables. Par exemple, Java connaît int, String, .. au moment de la compilation, tandis que le type d'un objet en JavaScript ne peut être déterminé qu'au moment de l'exécution

2. Typage fort ou faible

Strong - spécifie les restrictions sur les types de valeurs fournies à ses opérations (par exemple Java)

Faible - convertit (caste) les arguments d'une opération si ces arguments ont des types incompatibles (par exemple, Visual Basic)

Sachant que Java est un langage statique et faiblement typé, comment implémentez-vous des langages dynamiquement et fortement typés sur la JVM?

Le invokedynamic implémente un système d'exécution qui peut choisir l'implémentation la plus appropriée d'une méthode ou d'une fonction - une fois le programme compilé.

Exemple: ayant (a + b) et ne sachant rien sur les variables a, b au moment de la compilation, invokedynamic mappe cette opération à la méthode la plus appropriée en Java au moment de l'exécution. Par exemple, s'il s'avère que a, b sont des chaînes, appelez la méthode (String a, String b). S'il s'avère que a, b sont des entiers, alors appelez la méthode (int a, int b).

invokedynamic a été introduit avec Java 7.

Sabina Orazem
la source
4

Dans le cadre de mon article sur Java Records , j'ai expliqué la motivation derrière Inoke Dynamic. Commençons par une définition approximative d'Indy.

Présentation d'Indy

Invoke Dynamic (également connu sous le nom d' Indy ) faisait partie de JSR 292 visant à améliorer la prise en charge JVM des langages de type dynamique. Après sa première version en Java 7, l' invokedynamicopcode et ses java.lang.invokebagages sont largement utilisés par les langages dynamiques basés sur JVM comme JRuby.

Bien qu'indy soit spécialement conçu pour améliorer la prise en charge dynamique des langues, il offre bien plus que cela. En fait, il peut être utilisé partout où un concepteur de langage a besoin de toute forme de dynamicité, des acrobaties de type dynamique aux stratégies dynamiques!

Par exemple, les expressions Java 8 Lambda sont en fait implémentées en utilisant invokedynamic, même si Java est un langage de typage statique!

Bytecode définissable par l'utilisateur

Pendant un certain temps, JVM a pris en charge quatre types d'invocation de méthode: invokestaticpour appeler des méthodes statiques, invokeinterfacepour appeler des méthodes d'interface, invokespecialpour appeler des constructeurs super()ou des méthodes privées et invokevirtualpour appeler des méthodes d'instance.

Malgré leurs différences, ces types d'invocation partagent un trait commun: nous ne pouvons pas les enrichir avec notre propre logique . Au contraire, invokedynamic nous permet d'amorcer le processus d'appel de la manière que nous voulons. Ensuite, la JVM se charge d'appeler directement la méthode Bootstrapped.

Comment fonctionne Indy?

La première fois que JVM voit une invokedynamicinstruction, elle appelle une méthode statique spéciale appelée Méthode Bootstrap . La méthode bootstrap est un morceau de code Java que nous avons écrit pour préparer la logique à appeler:

entrez la description de l'image ici

Ensuite, la méthode bootstrap renvoie une instance de java.lang.invoke.CallSite. Cela CallSitecontient une référence à la méthode réelle, à savoir MethodHandle.

À partir de maintenant, chaque fois que JVM voit à invokedynamicnouveau cette instruction, il ignore le chemin lent et appelle directement l'exécutable sous-jacent. La machine virtuelle Java continue d'ignorer le chemin lent à moins que quelque chose ne change.

Exemple: enregistrements Java 14

Java 14 Recordsfournit une belle syntaxe compacte pour déclarer des classes censées être des détenteurs de données stupides.

Compte tenu de ce simple enregistrement:

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

Le bytecode pour cet exemple serait quelque chose comme:

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

Dans son tableau des méthodes 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

Ainsi, la méthode d' amorçage pour Records est appelée bootstrapqui réside dans la java.lang.runtime.ObjectMethodsclasse. Comme vous pouvez le voir, cette méthode de bootstrap attend les paramètres suivants:

  • Une instance de MethodHandles.Lookupreprésentation du contexte de recherche (la Ljava/lang/invoke/MethodHandles$Lookuppièce).
  • Le nom de la méthode (c. -à toString, equals, hashCode, etc.) , le bootstrap va lien. Par exemple, lorsque la valeur est toString, bootstrap renverra un ConstantCallSite(a CallSitequi ne change jamais) qui pointe vers l' toStringimplémentation réelle de cet enregistrement particulier.
  • Le TypeDescriptorpour la méthode ( Ljava/lang/invoke/TypeDescriptor partie).
  • Un jeton de type, c'est-à-dire Class<?>représentant le type de classe Record. C'est Class<Range>dans ce cas.
  • Une liste séparée par des points-virgules de tous les noms de composants, c.-à-d min;max.
  • Un MethodHandlepar composant. De cette façon, la méthode bootstrap peut créer un MethodHandlebasé sur les composants pour cette implémentation de méthode particulière.

L' invokedynamicinstruction transmet tous ces arguments à la méthode bootstrap. La méthode Bootstrap, à son tour, retourne une instance de ConstantCallSite. Ceci ConstantCallSitecontient une référence à la mise en œuvre de la méthode demandée, par exemple toString.

Pourquoi Indy?

Contrairement aux API Reflection, l' java.lang.invokeAPI est assez efficace car la JVM peut voir complètement toutes les invocations. Par conséquent, JVM peut appliquer toutes sortes d'optimisations tant que nous évitons autant que possible le chemin lent!

En plus de l'argument d'efficacité, l' invokedynamicapproche est plus fiable et moins fragile en raison de sa simplicité .

De plus, le bytecode généré pour les enregistrements Java est indépendant du nombre de propriétés. Donc, moins de bytecode et un temps de démarrage plus rapide.

Enfin, supposons qu'une nouvelle version de Java inclut une nouvelle implémentation de méthode de bootstrap plus efficace. Avec invokedynamic, notre application peut profiter de cette amélioration sans recompilation. De cette façon, nous avons une sorte de compatibilité binaire directe . C'est aussi la stratégie dynamique dont nous parlions!

Autres exemples

En plus des enregistrements Java, la dynamique d' appel a été utilisée pour implémenter des fonctionnalités telles que:

Ali Dehghani
la source