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' invokedynamic
opcode et ses java.lang.invoke
bagages 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: invokestatic
pour appeler des méthodes statiques, invokeinterface
pour appeler des méthodes d'interface, invokespecial
pour appeler des constructeurs super()
ou des méthodes privées et invokevirtual
pour 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 invokedynamic
instruction, 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:
Ensuite, la méthode bootstrap renvoie une instance de java.lang.invoke.CallSite
. Cela CallSite
contient une référence à la méthode réelle, à savoir MethodHandle
.
À partir de maintenant, chaque fois que JVM voit à invokedynamic
nouveau 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 Records
fournit 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 bootstrap
qui réside dans la java.lang.runtime.ObjectMethods
classe. Comme vous pouvez le voir, cette méthode de bootstrap attend les paramètres suivants:
- Une instance de
MethodHandles.Lookup
représentation du contexte de recherche (la Ljava/lang/invoke/MethodHandles$Lookup
piè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 CallSite
qui ne change jamais) qui pointe vers l' toString
implémentation réelle de cet enregistrement particulier.
- Le
TypeDescriptor
pour 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
MethodHandle
par composant. De cette façon, la méthode bootstrap peut créer un MethodHandle
basé sur les composants pour cette implémentation de méthode particulière.
L' invokedynamic
instruction transmet tous ces arguments à la méthode bootstrap. La méthode Bootstrap, à son tour, retourne une instance de ConstantCallSite
. Ceci ConstantCallSite
contient 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.invoke
API 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' invokedynamic
approche 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:
meth.invoke(args)
. Alors, comment ça vainvokedynamic
avecmeth.invoke
?