Clauses d'essai définies et démontrées

Bienvenue dans un autre épisode de Under The Hood . Cette colonne donne aux développeurs Java un aperçu des mystérieux mécanismes qui cliquent et vrombissent sous leurs programmes Java en cours d'exécution. L'article de ce mois poursuit la discussion sur le jeu d'instructions bytecode de la machine virtuelle Java (JVM). Elle se concentre sur la manière dont la JVM gère les finallyclauses et les codes d'octets qui sont pertinents pour ces clauses.

Enfin: de quoi se réjouir

Lorsque la machine virtuelle Java exécute les bytecodes qui représentent un programme Java, elle peut quitter un bloc de code - les instructions entre deux accolades correspondantes - de plusieurs manières. D'une part, la JVM pourrait simplement s'exécuter au-delà de l'accolade fermante du bloc de code. Ou, il peut rencontrer une instruction break, continue ou return qui le fait sauter hors du bloc de code depuis quelque part au milieu du bloc. Enfin, une exception peut être levée qui oblige la machine virtuelle Java à sauter vers une clause catch correspondante ou, s'il n'y a pas de clause catch correspondante, à mettre fin au thread. Avec ces points de sortie potentiels existant dans un seul bloc de code, il est souhaitable d'avoir un moyen facile d'exprimer que quelque chose s'est passé, quelle que soit la façon dont un bloc de code est sorti. En Java, un tel désir s'exprime avec untry-finally clause.

Pour utiliser une try-finallyclause:

  • enfermez dans un trybloc le code qui a plusieurs points de sortie, et

  • mettez dans un finallybloc le code qui doit se produire quelle que soit la façon dont le trybloc est sorti.

Par exemple:

try {// Bloc de code avec plusieurs points de sortie} finally {// Bloc de code qui est toujours exécuté lorsque le bloc try est quitté, // quelle que soit la façon dont le bloc try est quitté} 

Si vous avez des catchclauses associées au trybloc, vous devez mettre la finallyclause après toutes les catchclauses, comme dans:

try {// Bloc de code avec plusieurs points de sortie} catch (Cold e) {System.out.println ("Pris froid!"); } catch (APopFly e) {System.out.println ("Caught a pop fly!"); } catch (SomeonesEye e) {System.out.println ("J'ai attrapé l'œil de quelqu'un!"); } finally {// Bloc de code qui est toujours exécuté lorsque le bloc try est quitté, // quelle que soit la manière dont le bloc try est quitté. System.out.println ("Est-ce quelque chose à encourager?"); }

Si lors de l'exécution du code dans un trybloc, une exception est levée qui est gérée par une catchclause associée au trybloc, la finallyclause sera exécutée après la catchclause. Par exemple, si une Coldexception est levée pendant l'exécution des instructions (non affichées) dans le trybloc ci-dessus, le texte suivant sera écrit dans la sortie standard:

Pris froid! Est-ce quelque chose à encourager?

Clauses d'essai dans les bytecodes

Dans les bytecodes, les finallyclauses agissent comme des sous-routines miniatures dans une méthode. A chaque point de sortie à l'intérieur d'un trybloc et de ses catchclauses associées , le sous-programme miniature qui correspond à la finallyclause est appelé. Une fois la finallyclause terminée - tant qu'elle se termine en exécutant au-delà de la dernière instruction de la finallyclause, et non en lançant une exception ou en exécutant un retour, une poursuite ou une interruption - le sous-programme miniature lui-même retourne. L'exécution continue juste après le point où le sous-programme miniature a été appelé en premier lieu, de sorte que le trybloc peut être quitté de la manière appropriée.

L'opcode qui fait sauter la JVM à un sous-programme miniature est l' instruction jsr . L' instruction jsr prend un opérande de deux octets, le décalage par rapport à l'emplacement de l' instruction jsr où commence le sous-programme miniature. Une deuxième variante de l' instruction jsr est jsr_w , qui exécute la même fonction que jsr mais prend un opérande large (quatre octets). Lorsque la JVM rencontre une instruction jsr ou jsr_w , elle pousse une adresse de retour sur la pile, puis continue l'exécution au début du sous-programme miniature. L'adresse de retour est le décalage du bytecode qui suit immédiatement le jsr oujsr_w et ses opérandes.

Une fois qu'un sous-programme miniature est terminé, il appelle l' instruction ret , qui revient du sous-programme. L' instruction ret prend un opérande, un index dans les variables locales où l'adresse de retour est stockée. Les opcodes qui traitent des finallyclauses sont résumés dans le tableau suivant:

Enfin les clauses
Opcode Opérande (s) La description
jsr branchbyte1, branchbyte2 pousse l'adresse de retour, les branches à offset
jsr_w branchbyte1, branchbyte2, branchbyte3, branchbyte4 pousse l'adresse de retour, se branche sur un large décalage
ret indice retourne à l'adresse stockée dans l'index de la variable locale

Ne confondez pas un sous-programme miniature avec une méthode Java. Les méthodes Java utilisent un ensemble d'instructions différent. Des instructions telles que invokevirtual ou invokenonvirtual provoquent l' appel d' une méthode Java et des instructions telles que return , areturn ou ireturn provoquent le retour d' une méthode Java. L' instruction jsr ne provoque pas l' appel d' une méthode Java. Au lieu de cela, cela provoque un saut vers un opcode différent dans la même méthode. De même, l' instruction ret ne revient pas d'une méthode; au contraire, il revient à l'opcode dans la même méthode qui suit immédiatement l' instruction jsr appelante et ses opérandes. Les bytecodes qui implémentent unfinallysont appelés un sous-programme miniature car ils agissent comme un petit sous-programme dans le flux de bytecode d'une seule méthode.

Vous pourriez penser que l' instruction ret devrait sortir l'adresse de retour de la pile, car c'est là qu'elle a été poussée par l' instruction jsr . Mais ce n'est pas le cas. Au lieu de cela, au début de chaque sous-programme, l'adresse de retour est sautée du haut de la pile et stockée dans une variable locale - la même variable locale à partir de laquelle l' instruction ret l' obtient plus tard. Cette façon asymétrique de travailler avec l'adresse de retour est nécessaire parce que , enfin , les clauses (et , par conséquent, les sous - routines miniatures) eux - mêmes peuvent lancer des exceptions ou inclure return, breakou des continuedéclarations. En raison de cette possibilité, l'adresse de retour supplémentaire qui a été poussée sur la pile par le jsrl' instruction doit être retiré de la pile tout de suite, donc il ne sera pas toujours là si les finallysorties de clause avec break, continue, returnou exception levée. Par conséquent, l'adresse de retour est stockée dans une variable locale au début du finallysous-programme miniature de toute clause.

À titre d'illustration, considérez le code suivant, qui inclut une finallyclause qui se termine par une instruction break. Le résultat de ce code est que, quel que soit le paramètre bVal passé à la méthode surpriseTheProgrammer(), la méthode renvoie false:

statique booléen surpriseTheProgrammer (boolean bVal) {while (bVal) {try {return true; } enfin {pause; }} return false; }

L'exemple ci-dessus montre pourquoi l'adresse de retour doit être stockée dans une variable locale au début de la finallyclause. Comme la finallyclause se termine par une pause, elle n'exécute jamais l' instruction ret . En conséquence, la JVM ne revient jamais pour terminer l' return trueinstruction " ". Au lieu de cela, il continue simplement avec le breaket passe au-delà de l'accolade de fermeture de l' whileinstruction. La déclaration suivante est " return false," qui est précisément ce que fait la JVM.

Le comportement montré par une finallyclause qui se termine par un breakest également indiqué par des finallyclauses qui se terminent par un returnou continue, ou en lançant une exception. Si une finallyclause se termine pour l'une de ces raisons, l' instruction ret à la fin de la finallyclause n'est jamais exécutée. Étant donné que l' exécution de l' instruction ret n'est pas garantie, elle ne peut pas être invoquée pour supprimer l'adresse de retour de la pile. Par conséquent, l'adresse de retour est stockée dans une variable locale au début du finallysous-programme miniature de la clause.

Pour un exemple complet, considérez la méthode suivante, qui contient un trybloc avec deux points de sortie. Dans cet exemple, les deux points de sortie sont des returninstructions:

static int giveMeThatOldFashionedBoolean (boolean bVal) {try {if (bVal) {return 1; } retourne 0; } enfin {System.out.println ("Je suis démodé."); }}

La méthode ci-dessus se compile avec les bytecodes suivants:

// La séquence de bytecode pour le bloc try: 0 iload_0 // Pousser la variable locale 0 (arg passé comme diviseur) 1 ifeq 11 // Pousser la variable locale 1 (arg passé comme dividende) 4 iconst_1 // Push int 1 5 istore_3 // Pop an int (the 1), store into local variable 3 6 jsr 24 // Aller au mini-sous-programme pour la clause finally 9 iload_3 // Pousser la variable locale 3 (the 1) 10 ireturn // Renvoyer int au-dessus du stack (the 1) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), store into local variable 3 13 jsr 24 // Jump to the mini-subroutine for the finally clause 16 iload_3 // Push local variable 3 (le 0) 17 ireturn // Renvoie un entier au-dessus de la pile (le 0) // La séquence de bytecode pour une clause catch qui intercepte toute sorte d'exception // lancée depuis le bloc try. 18 astore_1 // Pop la référence à l'exception levée,store // dans la variable locale 1 19 jsr 24 // Aller à la mini-sous-routine pour la clause finally 22 aload_1 // Pousser la référence (à l'exception levée) depuis // la variable locale 1 23 athrow // Rejeter la même exception / / Le sous-programme miniature qui implémente le bloc finally. 24 astore_2 // Pop l'adresse de retour, stockez-la dans la variable locale 2 25 getstatic # 8 // Obtenez une référence à java.lang.System.out 28 ldc # 1 // Push depuis le pool de constantes 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Retour à l'adresse de retour stockée dans la variable locale 2le stocker dans la variable locale 2 25 getstatic # 8 // Obtenir une référence à java.lang.System.out 28 ldc # 1 // Pousser depuis le pool de constantes 30 invokevirtual # 7 // Appeler System.out.println () 33 ret 2 // Retour à l'adresse de retour stockée dans la variable locale 2le stocker dans la variable locale 2 25 getstatic # 8 // Obtenir une référence à java.lang.System.out 28 ldc # 1 // Pousser depuis le pool de constantes 30 invokevirtual # 7 // Appeler System.out.println () 33 ret 2 // Retour à l'adresse de retour stockée dans la variable locale 2

Les bytecodes du trybloc incluent deux instructions jsr . Une autre instruction jsr est contenue dans la catchclause. La catchclause est ajoutée par le compilateur car si une exception est levée lors de l'exécution du trybloc, le bloc finally doit toujours être exécuté. Par conséquent, la catchclause appelle simplement le sous-programme miniature qui représente la finallyclause, puis renvoie à nouveau la même exception. Le tableau des exceptions pour la giveMeThatOldFashionedBoolean()méthode, illustré ci-dessous, indique que toute exception levée entre et y compris les adresses 0 et 17 (tous les bytecodes qui implémentent le trybloc) est gérée par la catchclause commençant à l'adresse 18.

Tableau des exceptions: de à type de cible 0 18 18 au choix 

Les codes d'octets de la finallyclause commencent par extraire l'adresse de retour de la pile et la stocker dans la variable locale deux. À la fin de la finallyclause, l' instruction ret prend son adresse de retour à l'endroit approprié, la variable locale deux.

HopAround: une simulation de machine virtuelle Java

L'applet ci-dessous illustre une machine virtuelle Java exécutant une séquence de bytecodes. La séquence de bytecode dans la simulation a été générée par le javaccompilateur pour la hopAround()méthode de la classe indiquée ci-dessous:

class Clown {int statique hopAround () {int i = 0; while (true) {try {try {i = 1; } finally {// la première clause finally i = 2; } i = 3; return i; // cela ne se termine jamais, à cause de la continue} finally {// la deuxième clause finally if (i == 3) {continue; // this continue écrase l'instruction return}}}}}

Les bytecodes générés par javacpour la hopAround()méthode sont indiqués ci-dessous: