web-dev-qa-db-fra.com

Qu'est-ce qu'un cadre de carte de pile

J'ai récemment consulté The Java Virtual Machine Specifications (JVMS) pour essayer de mieux comprendre ce qui fait que mes programmes fonctionnent, mais j'ai trouvé une section que je ne comprends pas tout à fait .. .

La section 4.7.4 décrit l'attribut StackMapTable , et dans cette section, le document détaille les cadres de la carte de pile. Le problème est que c'est un peu verbeux et j'apprends mieux par l'exemple; pas en lisant.

Je comprends que le premier cadre de carte de pile est dérivé du descripteur de méthode, mais je ne comprends pas comment (ce qui est censé être expliqué ici .) De plus, je ne comprends pas entièrement ce que les cadres de carte de pile faire. Je suppose qu'ils sont similaires aux blocs en Java, mais il semble que vous ne puissiez pas avoir de cadres de carte empilés les uns dans les autres.

Quoi qu'il en soit, j'ai deux questions spécifiques:

  • Que font les cadres de carte de pile?
  • Comment est créé le premier cadre de carte de pile?

et une question générale:

  • Quelqu'un peut-il fournir une explication moins verbeuse et plus facile à comprendre que celle donnée dans le JVMS?
54
Steven Fontaine

Java nécessite que toutes les classes chargées soient vérifiées, afin de maintenir la sécurité du sandbox et de s'assurer que le code est sûr à optimiser. Notez que cela se fait au niveau du bytecode, donc la vérification ne pas vérifie les invariants du Java langage , il vérifie simplement que le bytecode a du sens selon les règles du bytecode.

Entre autres choses, la vérification de bytecode s'assure que les instructions sont bien formées, que tous les sauts sont des instructions valides dans la méthode et que toutes les instructions fonctionnent sur des valeurs du type correct. Le dernier est l'endroit où la carte de pile entre en jeu.

Le fait est que le bytecode en lui-même ne contient aucune information de type explicite. Les types sont déterminés implicitement par l'analyse du flux de données. Par exemple, une instruction iconst crée une valeur entière. Si vous le stockez dans l'emplacement 1, cet emplacement a maintenant un int. Si le flux de contrôle fusionne à partir d'un code qui y stocke un flottant, l'emplacement est désormais considéré comme ayant un type non valide, ce qui signifie que vous ne pouvez rien faire de plus avec cette valeur avant de l'écraser.

Historiquement, le vérificateur de bytecode a déduit tous les types à l'aide de ces règles de flux de données. Malheureusement, il est impossible de déduire tous les types en un seul passage linéaire à travers le bytecode car un saut en arrière pourrait invalider les types déjà déduits. Le vérificateur classique a résolu ce problème en itérant dans le code jusqu'à ce que tout cesse de changer, nécessitant potentiellement plusieurs passes.

Cependant, la vérification ralentit le chargement des classes en Java. Oracle a décidé de résoudre ce problème en ajoutant un nouveau vérificateur plus rapide, qui peut vérifier le bytecode en une seule passe. Pour ce faire, ils requis toutes les nouvelles classes commençant par Java 7 (avec Java 6 dans un état de transition) pour transporter des métadonnées) sur leurs types, afin que le bytecode puisse être vérifié en une seule passe. Comme le format du bytecode lui-même ne peut pas être modifié, ces informations de type sont stockées séparément dans un attribut appelé StackMapTable.

Le simple stockage du type pour chaque valeur à chaque point du code prendrait évidemment beaucoup de place et serait très coûteux. Afin de rendre les métadonnées plus petites et plus efficaces, ils ont décidé de les avoir ne lister que les types à des positions qui sont des cibles de sauts. Si vous y réfléchissez, c'est la seule fois où vous avez besoin des informations supplémentaires pour effectuer une vérification en un seul passage. Entre les cibles de saut, tout le flux de contrôle est linéaire, vous pouvez donc déduire les types entre les positions en utilisant les anciennes règles d'inférence.

Chaque position où les types sont explicitement répertoriés est connue comme un cadre de carte de pile. L'attribut StackMapTable contient une liste de trames dans l'ordre, bien qu'elles soient généralement exprimées comme une différence par rapport à la trame précédente afin de réduire la taille des données. S'il n'y a pas de trames dans la méthode, ce qui se produit lorsque le flux de contrôle ne se joint jamais (c'est-à-dire que le CFG est un arbre), alors l'attribut StackMapTable peut être complètement omis.

C'est donc l'idée de base de la façon dont StackMapTable fonctionne et pourquoi il a été ajouté. La dernière question est de savoir comment la trame initiale implicite est créée. La réponse est bien sûr qu'au début de la méthode, la pile d'opérandes est vide et les intervalles de variables locaux ont les types donnés par les types des paramètres de méthode, qui sont déterminés à partir du décrypteur de méthode.

Si vous êtes habitué à Java, il existe quelques différences mineures dans le fonctionnement des types de paramètres de méthode au niveau du bytecode. Tout d'abord, les méthodes virtuelles ont un this implicite comme premier paramètre. Deuxièmement, boolean, byte, char et short n'existent pas au niveau du bytecode. Au lieu de cela, ils sont tous mis en œuvre comme des intrus dans les coulisses.

121
Antimony