Tutoriel sur la compréhension de la machine virtuelle Java

Image non disponible


précédentsommairesuivant

V. Manipulation des variables locales

Au cours de cette cinquième partie, nous allons continuer notre voyage dans le monde des instructions bytecode Java.

Chaque cadre (frame) a un tableau de variables locales pouvant accueillir jusqu'à 65 536 entrées. Ceci signifie que la somme du nombre de paramètres et des variables temporaires d'une méthode ne peut excéder 65 536. Sachant que l'utilisation de variables de type long ou double réduit ce nombre. Bien évidemment, pour des raisons de performance, il est préférable d'utiliser des cases contiguës à partir de l'index 0 pour éviter à la JVM de créer des tableaux excessivement grands pour seulement quelques valeurs.

Les instructions load et store permettent de déplacer des valeurs de la pile vers les variables locales et vice versa. Plus précisément, load récupère une valeur des variables locales, puis l'empile, et store stocke la valeur au sommet de la pile dans les variables locales.

Le code est disponible sur Github (tag et branche).

V-A. Représentation de la pile

La JVM étant basée sur le modèle de la pile, il est essentiel de connaître quel est l'impact des instructions. Pour représenter l'état avant/après l'exécution d'une instruction, nous allons reprendre le format utilisé par la JVMS et qui est le suivant :

…, valeur1, valeur2 → …, résultat, où les valeurs le plus à droite sont au sommet de la pile. « valeur1 » et « valeur »2 étant les deux valeurs utilisées pour le calcul et « résultat » le résultat.

Il est important de noter que dans cette représentation le long et le double sont considérés comme une seule valeur. Par conséquent, lorsque nécessaire nous présenterons les différents cas d'utilisation d'une instruction en utilisant plusieurs formes.

V-B. Initialisation des variables locales

Comme nous l'avons vu dans la partie Part II - Introduction à la JVM, la JVM utilise les variables locales pour passer des paramètres à la méthode appelée. Lorsqu'une méthode de classe (statique) est appelée, tous les paramètres sont stockés dans les variables locales de manière consécutive à partir de l'index zéro. Lorsqu'une méthode d'instance est appelée, la variable à l'index zéro est toujours une référence de l'objet (this) sur laquelle la méthode a été appelée. Les paramètres sont quant à eux stockés de manière consécutive à partir de l'index 1.

Néanmoins, les variables locales étant aussi utilisées pour stocker des résultats partiels, les paramètres d'une méthode ou this peuvent être remplacés.

À noter que pour l'instant, PJBA permettant uniquement de générer des méthodes statiques, leurs paramètres sont accessibles à partir de l'index 0 des variables locales.

V-C. Load

Les instructions suivantes permettent de récupérer des valeurs depuis les variables locales. Les valeurs sont ajoutées au sommet de la pile (mais sont toujours disponibles dans les variables locales).

État de la pile avant → après exécution : … → valeur

Hex

Mnémonique

Argument

Description

0x15

iload

n

Empile la valeur de type int à l'index n

0x16

lload

n

Empile la valeur de type long à l'index n

0x17

fload

n

Empile la valeur de type float à l'index n

0x18

dload

n

Empile la valeur de type double à l'index n

0x19

aload

n

Empile la référence à l'index n

0x1a

iload_0

 

Empile la valeur de type int à l'index 0

0x1b

iload_1

 

Empile la valeur de type int à l'index 1

0x1c

iload_2

 

Empile la valeur de type int à l'index 2

0x1d

iload_3

 

Empile la valeur de type int à l'index 3

0x1e

lload_0

 

Empile la valeur de type long à l'index 0

0x1f

lload_1

 

Empile la valeur de type long à l'index 1

0x20

lload_2

 

Empile la valeur de type long à l'index 2

0x21

lload_3

 

Empile la valeur de type long à l'index 3

0x22

fload_0

 

Empile la valeur de type float à l'index 0

0x23

fload_1

 

Empile la valeur de type float à l'index 1

0x24

fload_2

 

Empile la valeur de type float à l'index 2

0x25

fload_3

 

Empile la valeur de type float à l'index 3

0x26

dload_0

 

Empile la valeur de type double à l'index 0

0x27

dload_1

 

Empile la valeur de type double à l'index 1

0x28

dload_2

 

Empile la valeur de type double à l'index 2

0x29

dload_3

 

Empile la valeur de type double à l'index 3

0x2a

aload_0

 

Empile la référence à l'index 0

0x2b

aload_1

 

Empile la référence à l'index 1

0x2c

aload_2

 

Empile la référence à l'index 2

0x2d

aload_3

 

Empile la référence à l'index 3

Voyons quelques exemples :

Empile une chaîne de caractères avec aload :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
.class org/isk/jvmhardcore/bytecode/partfour/Aload
  .method getThird(BILjava/lang/String;J)Ljava/lang/String;
    aload 2
    areturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Test
public void aload() {
  final String result = Aload.getThird((byte)1, 2, "Hello", 4l);
 
  Assert.assertEquals("Hello", result);
}

Source

Empile un long avec lload :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
.class org/isk/jvmhardcore/bytecode/partfour/Lload
  .method getFourth(BILjava/lang/String;J)J
    lload 3
    lreturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Test
public void lload() {
  final long result = Lload.getFourth((byte)1, 2, "Hello", 4l);
 
  Assert.assertEquals(4l, result);
}

Source

Empile l'int dans les variables à l'index 0 avec iload_0 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
.class org/isk/jvmhardcore/bytecode/partfour/Iload_0
  .method getFirst(III)I
    iload_0
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Test
public void iload_0() {
  final int result = Iload_0.getFirst(1, 2, 3);
 
  Assert.assertEquals(1, result);
}

Source

Empile l'int dans les variables à l'index 0 avec iload_0 et retourne un byte :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
.class org/isk/jvmhardcore/bytecode/partfour/Iload_0_Byte
  .method getFirst(BII)B
    iload_0
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Test
public void iload_0_byte() {
  final byte result = Iload_0_Byte.getFirst((byte) 1, 2, 3);
 
  Assert.assertEquals(1, result);
}

Source

Empile l'int dans les variables à l'index 0 avec iload_0 et retourne un char :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
.class org/isk/jvmhardcore/bytecode/partfour/Iload_0_Char
  .method getFirst(CII)C
    iload_0
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Test
public void iload_0_char() {
  final char result = Iload_0_Char.getFirst((char) 1, 2, 3);
 
  Assert.assertEquals(1, result);
}

Source

Empile l'int dans les variables à l'index 0 avec iload_0 et retourne un short :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
.class org/isk/jvmhardcore/bytecode/partfour/Iload_0_Short
  .method getFirst(SII)S
    iload_0
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Test
public void iload_0_short() {
  final short result = Iload_0_Short.getFirst((byte) 1, 2, 3);
 
  Assert.assertEquals(1, result);
}

Source

Essaie de retourner la valeur à l'index 0 des variables locales.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
.class org/isk/jvmhardcore/bytecode/partfour/Iload_0_NoArgs
  .method getFirst()I
    iload_0
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
@Test
public void iload_0_noargs() {
  try {
    Iload_0_NoArgs.getFirst();
    Assert.fail();
  } catch (VerifyError e) {
    // Assertion
    // Message retourné par la JVM:
    //   (class: org/isk/jvmhardcore/bytecode/partfour/Iload_0_NoArgs, 
    //   method: getFirst signature: ()I) Accessing value from uninitialized register 0"
  }
}

Source

La méthode n'ayant pas de paramètre et aucune instruction de stockage dans les variables locales n'ayant été effectuée, il n'y a rien à l'index 0. Par conséquent, une exception est levée.

Empile l'int dans les variables à l'index 1 avec iload_1 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
.class org/isk/jvmhardcore/bytecode/partfour/Iload_1
  .method getSecond(III)I
    iload_1
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Test
public void iload_1() {
  final int result = Iload_1.getSecond(1, 2, 3);
 
  Assert.assertEquals(2, result);
}

Source

V-D. Store

Les instructions suivantes permettent de stocker la variable au sommet de la pile dans les variables locales. La valeur au sommet de la pile est supprimée.

État de la pile avant → après exécution : …, valeur → …

Hex

Mnémonique

Argument

Description

0x36

istore

n

Stocke la valeur de type int au sommet de la pile à l'index n

0x37

lstore

n

Stocke la valeur de type long au sommet de la pile à l'index n

0x38

fstore

n

Stocke la valeur de type float au sommet de la pile à l'index n

0x39

dstore

n

Stocke la valeur de type double au sommet de la pile à l'index n

0x3a

astore

n

Stocke la référence au sommet de la pile à l'index n

0x3b

istore_0

 

Stocke la valeur de type int au sommet de la pile à l'index 0

0x3c

istore_1

 

Stocke la valeur de type int au sommet de la pile à l'index 1

0x3d

istore_2

 

Stocke la valeur de type int au sommet de la pile à l'index 2

0x3e

istore_3

 

Stocke la valeur de type int au sommet de la pile à l'index 3

0x3f

lstore_0

 

Stocke la valeur de type long au sommet de la pile à l'index 0

0x40

lstore_1

 

Stocke la valeur de type long au sommet de la pile à l'index 1

0x41

lstore_2

 

Stocke la valeur de type long au sommet de la pile à l'index 2

0x42

lstore_3

 

Stocke la valeur de type long au sommet de la pile à l'index 3

0x43

fstore_0

 

Stocke la valeur de type float au sommet de la pile à l'index 0

0x44

fstore_1

 

Stocke la valeur de type float au sommet de la pile à l'index 1

0x45

fstore_2

 

Stocke la valeur de type float au sommet de la pile à l'index 2

0x46

fstore_3

 

Stocke la valeur de type float au sommet de la pile à l'index 3

0x47

dstore_0

 

Stocke la valeur de type double au sommet de la pile à l'index 0

0x48

dstore_1

 

Stocke la valeur de type double au sommet de la pile à l'index 1

0x49

dstore_2

 

Stocke la valeur de type double au sommet de la pile à l'index 2

0x4a

dstore_3

 

Stocke la valeur de type double au sommet de la pile à l'index 3

0x4b

astore_0

 

Stocke la référence au sommet de la pile à l'index 0

0x4c

astore_1

 

Stocke la référence au sommet de la pile à l'index 1

0x4d

astore_2

 

Stocke la référence au sommet de la pile à l'index 2

0x4e

astore_3

 

Stocke la référence au sommet de la pile à l'index 3

Contrairement à Java, les variables sont typées dynamiquement. Par conséquent, il est possible de stocker les cinq types connus de la JVM à n'importe quel index des variables locales. Une variable locale peut donc avoir des valeurs différentes à différents moments.

Prenons le code suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
.class org/isk/jvmhardcore/bytecode/partfour/Reassign
  .method reassign()D
    ldc "ma chaîne"
    astore_0
    aload_0
    dconst_1
    dstore_0
    dload_0
    dreturn
  .methodend
.classend

Source

Voyons ce qu'il se passe dans le cadre contenant la méthode reassign() :

Pour finir testons la méthode reassign() :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Test
public void reassign() {
  final double result = Reassign.reassign();
 
  Assert.assertEquals(1.0, result, 0.00001);
}

Source

Tout comme de nombreuses instructions que nous avons vues dans la partie précédente, load et store sont liées à des types. Ces types sont identifiables - le plus souvent - grâce à la première lettre de la mnémonique de l'instruction. De fait, il est nécessaire de faire attention à utiliser l'instruction correspondant aux types des valeurs présentes dans la pile et qui seront utilisées comme opérandes.

 
Sélectionnez
1.
2.
ldc  "ma chaîne"
istore 0            # Erreur! astore doit être utilisé

Les variables de type long et double prennent deux entrées dans la pile et les variables locales.

 
Sélectionnez
1.
2.
3.
4.
5.
ldc  "ma chaîne"
astore_2
ldc2_w 3.14d
dstore_1
aload_2         # Erreur! Une partie du double a remplacé "ma chaîne"

Pour finir notre tour des points d'attention, il est important de noter qu'une variable locale doit être initialisée avant d'être utilisée.

 
Sélectionnez
1.
2.
3.
iconst_5    # Empile 5
istore_3    # Stocke 5 dans la variable locale à l'index 3
iload_3     # Récupère la valeur (5) stockée dans la variable locale à l'index 3

Pour rappel, le tableau des variables locales - d'un cadre - est automatiquement initialisé par la JVM avec les paramètres de la méthode en cours d'exécution, si elle en a.

V-E. What's next ?

Dans la partie suivante, nous verrons des instructions permettant d'effectuer des opérations sur des nombres, mais aussi de changer le type des nombres.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2016 SOAT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.