Une instruction de machine virtuelle Java se compose d'un code d'opération d'une longueur d'octet (Opcode) qui représente une signification spécifique, suivi de zéro ou plusieurs opérandes qui représentent les paramètres d'opération. De nombreuses instructions dans la machine virtuelle ne contiennent pas d'opérandes, seulement un opcode. Si l'exception est ignorée, l'interpréteur JVM peut fonctionner efficacement avec un seul code.
Copiez le code comme suit :
faire{
Calculez automatiquement le registre PC et récupérez l'opcode à partir de l'emplacement du registre PC
si (l'opérande existe) supprime l'opérande ;
Effectuer l'opération définie par l'opcode ;
} while (traiter la boucle suivante)
Le nombre et la longueur des opérandes dépendent de l'opcode. Si la longueur d'un opérande dépasse un octet, il sera stocké dans l'ordre Big-Endian (premier bytecode haut de gamme) et sa valeur doit être (byte1<<8) | octet2.
Le flux d'instructions de bytecode est aligné sur un seul octet, à l'exception des instructions "tableswitch" et "lookupswitch". Leurs opérandes sont spéciaux et sont divisés par 4 octets. Les espaces correspondants doivent être réservés pour obtenir l'alignement.
Limiter la longueur de l'opcode de la machine virtuelle Java à un octet et renoncer à l'alignement de la longueur des paramètres du code compilé revient à obtenir un code compilé court et léger, même si cela peut coûter à l'implémentation de la JVM un certain coût en termes de performances. Étant donné que l'opcode ne peut avoir qu'un octet de longueur, il limite le nombre de jeux d'instructions et ne suppose pas que les données sont bien alignées, ce qui signifie que lorsque les données dépassent un octet, la structure de données spécifique doit être reconstruite à partir de les octets. Certaines performances seront perdues.
Types de données et machine virtuelle Java
Dans le jeu d'instructions de la JVM, la plupart des instructions contiennent des informations sur le type de données correspondant à leurs opérations. Par exemple, l'instruction iload charge les données de type int de la table de variables locales dans la pile d'opérandes, tandis que fload charge les données de type float.
Pour la plupart des instructions de bytecode liées aux types de données, leurs mnémoniques d'opcode ont des caractères spéciaux pour indiquer : i représente le type int, l représente long, s représente court, b représente octet, c représente char, f représente float, d représente double et a représente la référence. Il existe des directives distinctes qui peuvent être utilisées pour convertir les types non pris en charge en types pris en charge si nécessaire.
instructions de chargement et de stockage
Les instructions de chargement et de stockage sont utilisées pour transférer des données vers et depuis la table de variables locales du cadre de pile et la pile d'opérandes.
1) Les instructions pour charger une variable locale dans la pile d'opérandes incluent : iload, iload_<n>, lload, lload_<n>, float, fload_<n>, dload, dload_<n>, aload, aload_<n>.
2) Instructions pour stocker une valeur de la pile d'opérandes dans la variable locale : istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
3) Instructions pour charger des constantes dans la pile d'opérandes : bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, Iconst_ml, Iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
4) Instruction d'index d'accès de la table de variables locales : large
Certaines instructions se terminant par des crochets angulaires représentent un groupe d'instructions, comme iload_<i>, qui représente iload_0, iload_1, etc. Ces groupes d'instructions sont des instructions générales avec un seul opérande.
Mode d'emploi
Les instructions arithmétiques sont utilisées pour effectuer une opération spécifique sur les valeurs des deux piles d'opérandes et stocker le résultat en haut de la pile d'opérations.
1) Instructions d'ajout : iadd,ladd,fadd,dadd
2) Instructions de soustraction : isub, lsub, fsub, dsub
3) Instructions de multiplication : imul, lmul, fmul, dmul
4) Instructions de division : idiv, ldiv, fdiv, ddiv
5) Instructions restantes : irem, lrem, frem, drem
6) Instructions négatives : ineg, length, fneg, dneg
7) Instructions de déplacement : ishl,ishr,iushr,lshl,lshr,lusr
8) Instructions OU au niveau du bit : ior, lor
9) Instructions ET au niveau du bit : iand, atterrir
10) Instructions XOR au niveau du bit : ixor, lxor
11) Instruction d'incrémentation de variable locale : iinc
12) Instructions de comparaison : dcmpg, dcmpl, fcmpg, fcmpl, lcmp
La machine virtuelle Java ne stipule pas clairement le débordement de données entières, mais elle stipule que lors du traitement de données entières, seules les instructions de division et de reste amèneront la machine virtuelle à lever une exception lorsque le diviseur est 0.
instructions de chargement et de stockage
Les instructions de chargement et de stockage sont utilisées pour transférer des données vers et depuis la table de variables locales du cadre de pile et de la pile d'opérandes.
1) Les instructions pour charger une variable locale dans la pile d'opérandes incluent : iload, iload_<n>, lload, lload_<n>, float, fload_<n>, dload, dload_<n>, aload, aload_<n>.
2) Instructions pour stocker une valeur de la pile d'opérandes dans la variable locale : istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
3) Instructions pour charger des constantes dans la pile d'opérandes : bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, Iconst_ml, Iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
4) Instruction d'index d'accès de la table de variables locales : large
Certaines instructions se terminant par des crochets angulaires représentent un groupe d'instructions, comme iload_<i>, qui représente iload_0, iload_1, etc. Ces groupes d'instructions sont des instructions générales avec un seul opérande.
Mode d'emploi
Les instructions arithmétiques sont utilisées pour effectuer une opération spécifique sur les valeurs des deux piles d'opérandes et stocker le résultat en haut de la pile d'opérations.
1) Instructions d'ajout : iadd,ladd,fadd,dadd
2) Instructions de soustraction : isub, lsub, fsub, dsub
3) Instructions de multiplication : imul, lmul, fmul, dmul
4) Instructions de division : idiv, ldiv, fdiv, ddiv
5) Instructions restantes : irem, lrem, frem, drem
6) Instructions négatives : ineg, length, fneg, dneg
7) Instructions de déplacement : ishl,ishr,iushr,lshl,lshr,lusr
8) Instructions OU au niveau du bit : ior, lor
9) Instructions ET au niveau du bit : iand, atterrir
10) Instructions XOR au niveau du bit : ixor, lxor
11) Instruction d'incrémentation de variable locale : iinc
12) Instructions de comparaison : dcmpg, dcmpl, fcmpg, fcmpl, lcmp
La machine virtuelle Java ne stipule pas clairement le débordement de données entières, mais elle stipule que lors du traitement de données entières, seules les instructions de division et de reste amèneront la machine virtuelle à lever une exception lorsque le diviseur est 0.
instructions de conversion de type
Les instructions de conversion de type convertissent deux types numériques de machines virtuelles Java entre eux. Ces opérations sont généralement utilisées pour implémenter des opérations de conversion de type explicites dans le code utilisateur.
JVM prend en charge la conversion de type élargie (conversion de type à petite plage en conversion de type à grande plage) :
1) Type Int à type long, flottant, double
2) type long pour flotter, type double
3) flotter pour doubler le type
Instructions de conversion de type étroit : i2b, i2c, i2s, l2i, f2i, f2l, d2l et d2f. La conversion de type étroit peut entraîner la production de signes et d'ordres de grandeur différents. Le processus de conversion peut entraîner une perte de précision de la valeur numérique. Par exemple, lorsque le type int ou long est converti en type entier T, le processus de conversion ignore uniquement le contenu inattendu des N octets les plus bas (N est la longueur du type de données du type T).
Création et manipulation d'objets
Bien que les instances de classe et les tableaux soient tous deux des objets, la machine virtuelle Java utilise des instructions de bytecode différentes pour la création et le fonctionnement des instances de classe et des tableaux.
1) Instruction pour créer une instance : nouveau
2) Instructions pour créer des tableaux : newarray, anewarray, multianewarray
3) Accéder aux instructions du champ : getfield, putfield, getstatic, putstatic
4) Chargez les éléments du tableau dans les instructions de la pile d'opérandes : baload, caload, saload, iaload, laload, faload, daload, aaload
5) Stockez la valeur de la pile d'opérandes dans l'élément du tableau et exécutez : bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
6) Récupérez l'instruction de longueur du tableau : arraylength
7) Vérifiez les instructions de type d'instance : instanceof, checkcast
Instructions de gestion de la pile d'opérandes
Instructions qui exploitent directement la pile d'opérandes : pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2 et swap
instruction de transfert de contrôle
Permet à la JVM de poursuivre l'exécution du programme de manière conditionnelle ou inconditionnelle à partir de l'instruction suivant l'instruction spécifiée plutôt que de l'instruction de transfert de contrôle. Les instructions de transfert de contrôle comprennent :
1) Branchement conditionnel : ifeq, iflt, ifle, ifne, ifgt, ifge, ifnull, ifnotnull, if_cmpeq, if_icmpne, if_icmlt, if_icmpgt, etc.
2) Branche conditionnelle composée : tableswitch, lookupswitch
3) Branche inconditionnelle : goto,goto_w,jsr,jsr_w,ret
Il existe un jeu d'instructions spéciales dans la JVM pour gérer les opérations de comparaison de branches conditionnelles des types int et référence. Afin d'indiquer clairement si une valeur d'entité est nulle, il existe des instructions spéciales pour détecter les valeurs nulles. Les opérations de comparaison de branchement conditionnel de type booléen et de type octet, de type char et de type court sont toutes effectuées à l'aide d'instructions de comparaison de type int, tandis que les opérations de comparaison de branchement conditionnel long, float et double sont effectuées par des instructions d'opération de comparaison du type correspondant, et l'opération les instructions renverront une valeur entière. La valeur entière est ajoutée à la pile d'opérandes, puis une opération de comparaison conditionnelle de type int est effectuée pour terminer l'intégralité du saut de branche. Tous les types de comparaisons seront éventuellement convertis en opérations de comparaison de type int.
Appel de méthode et instructions de retour
Instruction d'invocation virtuelle : Appelez la méthode d'instance de l'objet et répartissez-la en fonction du type réel de l'objet (répartition de machine virtuelle).
Instruction d'invocation d'interface : appelez la méthode d'interface, recherchez un objet qui implémente cette méthode d'interface au moment de l'exécution et trouvez la méthode appropriée à appeler.
Invoquer spécial : appeler des méthodes d'instance qui nécessitent un traitement spécial, notamment les méthodes d'initialisation d'instance, les méthodes privées et les méthodes de classe parent.
invoquerstatique : appeler la méthode de classe (statique)
Les instructions de retour de méthode se distinguent en fonction du type de valeur de retour, notamment ireturn (la valeur de retour est booléenne, byte, char, short et int), lreturn, freturn, drturn et areturn. L'autre retour concerne la méthode void, la méthode d'initialisation d'instance. , classe et La méthode d'initialisation de classe i de l'interface est utilisée.
synchrone
La JVM prend en charge la synchronisation au niveau de la méthode et la synchronisation d'une séquence d'instructions au sein d'une méthode, toutes deux implémentées via des moniteurs.
La synchronisation au niveau de la méthode est implicite et n'a pas besoin d'être contrôlée via des instructions de bytecode. Elle est implémentée dans les appels de méthode et les opérations de retour. La machine virtuelle distingue s'il s'agit d'une méthode synchrone grâce à l'indicateur ACC_SYNCHRONIZED dans la structure standard de la méthode dans le pool de constantes de méthode. Lorsque la méthode est appelée, l'instruction appelante vérifiera si l'indicateur est défini. S'il est défini, le thread d'exécution maintient le moniteur, puis exécute la méthode et libère enfin le moniteur lorsque la méthode est terminée.
Synchronisez une séquence de jeux d'instructions, généralement marqués par un bloc synchronisé. Le jeu d'instructions JVM a Monitorenter et Monitorexit pour prendre en charge la sémantique synchronisée.
Le verrouillage structuré se produit lorsque chaque sortie de moniteur lors d'un appel de méthode correspond à l'entrée précédente du moniteur. La JVM garantit que les verrous structurés sont établis via les deux règles suivantes (T représente un thread, M représente un moniteur) :
1) Le nombre de fois où T maintient M pendant l'exécution de la méthode doit être égal au nombre de fois où T libère M lorsque la méthode est terminée.
2) À tout moment, il n’y aura jamais de situation où T libère M plus de fois que T ne détient M.