Compréhension approfondie de la machine virtuelle Java _ chapitre huit _ moteur d'exécution bytecode de la machine virtuelle

Notes basées sur << Une compréhension approfondie de la machine virtuelle Java >>

Aperçu

Le moteur d'exécution de la machine physique est directement construit sur le niveau du système d'exploitation du processeur, du cache et du jeu d'instructions

Le moteur d'exécution de la machine virtuelle est implémenté par un logiciel, qui peut personnaliser la structure du jeu d'instructions et du moteur d'exécution sans être limité par des conditions physiques, et peut exécuter des formats de jeu d'instructions qui ne sont pas directement pris en charge par le matériel

Lorsque le moteur d'exécution exécute le bytecode, il existe deux options: l'interprétation, l'exécution et la compilation. Mais l'entrée et la sortie sont les mêmes: le flux binaire d'entrée bytecode, le processus de traitement est le processus équivalent d'analyse et d'exécution de bytecode, et la sortie est le résultat de l'exécution

Structure du cadre de la pile d'exécution

La machine virtuelle prend une méthode comme unité d'exécution la plus élémentaire, et le cadre de pile correspond à une méthode, qui est un élément de pile de la pile de machine virtuelle dans la zone de données de la machine virtuelle lorsqu'elle est en cours d'exécution. Le processus de chaque méthode du début de l'appel à la fin de l'exécution correspond au processus d'un frame de pile de la pile à la pile dans la pile de machine virtuelle

Le cadre de la pile stocke la table des variables locales, la pile d'opérandes, la connexion dynamique, l'adresse de retour de méthode et des informations supplémentaires

La quantité de mémoire à allouer pour une trame de pile est calculée lorsque le code source du programme est compilé et écrit dans l'attribut Code de la table de méthodes. Il ne sera pas affecté par les données de variable pendant l'exécution du programme, mais dépend uniquement du code source du programme et de la valeur virtuelle spécifique Disposition de la mémoire de pile

Du point de vue d'un programme Java, en même temps, sur le même thread, toutes les méthodes de la pile d'appels sont dans l'état d'exécution en même temps

Pour le moteur d'exécution, dans le thread actif, seule la méthode en haut de la pile est en cours d'exécution, qui est appelée le frame de pile actuel et la méthode actuelle

Table des variables locales

Est un espace de stockage pour un ensemble de valeurs de variables, utilisé pour stocker les paramètres de méthode et les variables locales définies dans la méthode

Lorsque le programme Java est compilé dans un fichier de classe, la capacité maximale de la table de variables locales que la méthode doit être allouée est déterminée dans la donnée max_locals de l'attribut Code de la méthode

La table de variables locales est la plus petite unité de l'emplacement variable, un emplacement variable peut stocker un type de données dans les 32 bits

Au moins deux choses doivent être faites pour citer:

	1.  根据引用直接或间接地查找对象在Java堆中的数据存放的起始地址或索引
	2.  根据引用直接或间接地查找对象所属数据类型在方法区中的存储的类型信息

Pour les données 64 bits, deux espaces de créneaux variables consécutifs (long, double) seront alloués et les bits séparés seront lus et écrits deux fois pour les données 32 bits. Étant donné que la table de variables locales est construite dans la pile de threads et appartient à des données privées de thread, peu importe si deux emplacements variables consécutifs sont des opérations atomiques, cela ne causera pas de problèmes de concurrence des données et de sécurité des threads.

Lorsque la méthode est appelée, la machine virtuelle utilise la table de variables locales pour terminer le processus de transfert des valeurs de paramètres vers la liste de variables de paramètres, c'est-à-dire le transfert des paramètres réels vers des paramètres formels. S'il s'agit d'une méthode d'instance, l'emplacement de variable avec le 0ème index de la table des variables locales stocke la référence à l'instance d'objet à laquelle appartient la méthode par défaut, c'est-à-dire

Les emplacements variables peuvent être réutilisés et ceux qui sont hors de portée peuvent être réaffectés

La table des variables locales n'a pas d'étape de préparation, donc si une variable locale est définie mais qu'aucune valeur initiale n'est affectée, elle ne peut pas être utilisée. Le compilateur peut vérifier et demander ce point lors de la compilation.

Pile d'opérande

La profondeur maximale est également écrite dans max_stacks de l'attribut Code au moment de la compilation

La capacité de pile occupée par les types de données 32 bits est de 1 et 64 est occupée par 2, et la profondeur de la pile d'opérandes ne dépassera à aucun moment la valeur maximale définie par max_stacks

Lorsque vous effectuez des opérations arithmétiques, en poussant l'opérande impliqué dans l'opération sur le dessus de la pile, puis en appelant l'instruction d'opération

Par exemple, l'instruction iadd nécessite que deux types int aient été stockés dans les éléments du haut et du deuxième haut de la pile d'opérandes au moment de l'exécution. L'exécution de cette instruction fera sortir les deux entiers de la pile et les ajoutera, puis les réinsérera dans la pile.

Lien dynamique

Afin de prendre en charge la connexion dynamique lors de l'appel de méthode, chaque cadre de pile contient une référence à la méthode à laquelle appartient le cadre de pile dans le pool de constantes d'exécution.

Adresse de retour de la méthode

Deux façons de quitter:

Achèvement d'appel normal: le moteur d'exécution rencontre une instruction bytecode retournée par n'importe quelle méthode, puis une valeur de retour peut être transmise à l'appelant de la méthode supérieure

Appel d'exception terminé: une exception a été rencontrée lors de l'exécution de la méthode et aucun gestionnaire d'exceptions correspondant n'a été trouvé dans la table des exceptions de la méthode

Une fois la méthode terminée, elle doit revenir à la position où la méthode d'origine a été appelée. S'il s'agit d'une sortie normale, la valeur du compteur PC de la méthode principale est enregistrée

Lorsque la méthode se termine: restaurez la table des variables locales et la pile d'opérandes de la méthode supérieure, poussez la valeur de retour dans la pile d'opérandes du cadre de pile de l'appelant, ajustez la valeur du compteur PC pour pointer vers une instruction après l'instruction d'appel de méthode

informations supplémentaires

Certaines informations qui ne sont pas décrites dans la spécification peuvent être ajoutées au cadre de la pile, telles que les informations relatives au débogage et à la collecte des performances

En règle générale, la connexion dynamique, l'adresse de retour de méthode et les informations supplémentaires sont regroupées dans une seule catégorie, appelée informations de trame de pile

Appel de méthode

Déterminez la version de la méthode appelée (c'est-à-dire quelle méthode est appelée) et le processus d'opération spécifique à l'intérieur de la méthode n'est pas impliqué pour le moment

Tous les appels de méthode stockés dans le fichier de classe ne sont que des références symboliques, plutôt que l'adresse d'entrée de la méthode dans la structure de la mémoire d'exécution réelle (c'est-à-dire une référence directe)

Ainsi, certains appels doivent avoir lieu pendant le chargement de la classe ou même pendant l'exécution pour déterminer la référence directe de la méthode cible

Analyse

Dans la phase d'analyse de la classe, certaines des références de symboles seront converties en références directes, à condition que ces méthodes aient une version déterminable de l'appel avant que le programme ne soit réellement exécuté, et cela ne changera pas pendant l'exécution

Conformez-vous à "Connaissable à la compilation et immuable à l'exécution", il existe principalement deux catégories: les méthodes statiques et les fichiers privés. La première est directement liée au type, et la seconde n'est pas accessible de l'extérieur.

Appeler l'instruction de bytecode

  • invokestatic est utilisé pour appeler des méthodes statiques
  • invokespecial est utilisé pour appeler la méthode (), la méthode privée, la méthode de la classe parent
  • invokevirtual est utilisé pour appeler toutes les méthodes virtuelles
  • invokeinterface est utilisé pour appeler des méthodes d'interface, et un objet qui implémente l'interface sera déterminé au moment de l'exécution
  • Invokedynamic résout d'abord dynamiquement la méthode référencée par le qualificatif de site d'appel au moment de l'exécution, puis l'exécute

Tant que la méthode peut être appelée par les instructions invokestatic et invokespecial, la version d'appel unique peut être déterminée dans la phase d'analyse

Il existe un total de méthodes qui remplissent ces conditions: méthodes statiques, méthodes privées, constructeurs d'instances, méthodes parentes, ainsi que des méthodes modifiées par final (bien qu'elles soient modifiées par invokevirtual)

Ces cinq types de méthodes peuvent résoudre des références symboliques en références directes de la méthode lors du chargement de la classe, collectivement appelées méthodes non virtuelles

L'appel de résolution doit être un processus statique

Envoi

Envoi statique

static abstract class Human
{
}
static class Man extends Human
{
}
static class Woman extends Human
{
}

public void sayHello(Human guy)
{
    System.out.println("Human");
}
public void sayHello(Man guy)
{
    System.out.println("Man");
}
public void sayHello(Woman guy)
{
    System.out.println("Women");
}

public static void main(String[] args)
{
    Human man = new Man();
    Human women = new Woman();
    MainTest mainTest = new MainTest();
    mainTest.sayHello(man);
    mainTest.sayHello(women);
    /*
    result:
        Human
        Human
     */
}

Human man = new Man();Dans, l'humain est appelé type statique, l'homme est appelé type réel

Le type statique final est connu lors de la recompilation, et le résultat du changement de type réel ne peut être déterminé qu'au moment de l'exécution

Lorsque la machine virtuelle est surchargée, le type statique du paramètre est utilisé comme base de jugement au lieu du type réel. Dans la phase de compilation, le compilateur décide quelle version surchargée utiliser en fonction du type statique du paramètre

Toutes les actions de répartition qui reposent sur des types statiques pour déterminer la version de l'exécution de la méthode sont appelées répartition statique. L'application la plus courante est la surcharge de méthode.

Bien que le compilateur puisse déterminer la version surchargée de la méthode, dans de nombreux cas, la version surchargée n'est pas unique et ne peut déterminer qu'une version relativement plus appropriée.

Envoi dynamique

C'est la réalisation de la réécriture en polymorphisme.

Distribuer la version d'exécution de la méthode en fonction du type réel de la variable au moment de l'exécution

Le champ ne participe jamais au polymorphisme

Le langage Java actuel est un langage statique à envoi multiple, un langage dynamique à envoi unique

Processus d'analyse virtuelle invokevirtual

  1. Trouvez le type réel de l'objet pointé par le premier élément en haut de la pile d'opérandes, noté C
  2. Si une méthode qui correspond au descripteur et au nom simple de la constante est trouvée dans le type C, la vérification de l'autorisation d'accès est effectuée, et si elle réussit, la référence directe de cette méthode est renvoyée et le processus de recherche se termine. IllegalAccessError est renvoyé en cas d'échec
  3. Sinon, selon la relation d'héritage, la deuxième étape du processus de recherche et de vérification est effectuée sur chaque classe parente de C de bas en haut
  4. Si aucune méthode appropriée n'est trouvée, une AbstractMethodError est levée

Réalisation d'envoi dynamique de machines virtuelles

Une méthode d'optimisation courante consiste à créer une table de méthode virtuelle (vtable) dans la zone de méthode et à utiliser l'index de table de méthode virtuelle au lieu de métadonnées pour améliorer les performances de recherche.

La table de méthodes virtuelles stocke l'adresse d'entrée réelle de chaque méthode. Si une méthode n'est pas remplacée dans la sous-classe, l'entrée d'adresse dans la table de méthodes virtuelles de la sous-classe est la même que l'entrée d'adresse de la même méthode dans la classe parente, et elles pointent toutes vers L'entrée de réalisation de la classe parent.

Si la sous-classe est remplacée, elle est remplacée par l'adresse d'entrée pointant vers la version d'implémentation de la sous-classe.

La table des méthodes virtuelles est généralement initialisée lors de la phase de connexion du chargement de la classe. Après avoir préparé la valeur initiale de la variable de classe, la machine virtuelle initialise également la table des méthodes virtuelles de la classe.

Les méthodes par défaut sans modification finale sont des méthodes virtuelles

Prise en charge des langues à typage dynamique

Instruction dynamique appelée, générée pour obtenir une prise en charge du langage de type dynamique

Langue typée dynamiquement

La principale caractéristique est la suivante: le processus principal de sa vérification de type est effectué au moment de l'exécution plutôt qu'au moment de la compilation

Exception d'exécution: tant que le code ne s'exécute pas sur cette ligne, aucune exception ne sera générée

Exception lors de la connexion: même si le code est placé sur une branche de chemin qui ne peut pas être exécutée du tout, une exception sera toujours levée lorsque la classe est chargée

Une autre caractéristique principale des langages à typage dynamique: les variables n'ont pas de type et les valeurs de variable ont des types

Avantages et inconvénients

  1. Les langages à typage statique peuvent déterminer les types de variables lors de la compilation, et les compilateurs peuvent améliorer la vérification de type complète et rigoureuse, ce qui favorise la stabilité et permet aux projets d'atteindre plus facilement des échelles plus grandes
  2. Le type est déterminé uniquement pendant l'exécution d'un langage à typage dynamique, ce qui offre aux développeurs une grande flexibilité et une grande clarté, ce qui signifie que l'efficacité du développement est améliorée

java.lang.invoke 包

La différence entre MethodHandle et Reflection

  • Reflection est un appel de méthode qui est simulé au niveau du code Java, et MethodHandle est un appel de méthode qui simule le niveau de bytecode
  • Reflection est une image complète du côté Java, qui est lourd, et MethodHandle est léger
  • La réflexion est difficile à optimiser, MethodHandle peut réaliser diverses optimisations (telles que l'inlining de méthode, etc.)
  • Reflection ne sert que le langage Java et MethodHandle est conçu pour servir tous les langages de machine virtuelle Java

Moteur d'exécution d'interprétation de bytecode basé sur la pile

Discutez de la manière d'exécuter les instructions de bytecode dans la méthode. Il existe deux types d'interprétation, d'exécution et de compilation.

Expliquer l'exécution

IMG_20200825_184807

Avant l'exécution, l'analyse lexicale et l'analyse syntaxique sont effectuées sur le code source du programme, et le code source est converti en une arborescence de répertoires abstraite.

Jeu d'instructions basé sur la pile et jeu d'instructions basé sur le registre

Le flux d'instructions bytecode produit par le compilateur Javac est essentiellement une architecture de jeu d'instructions basée sur la pile. La plupart du flux d'instructions de bytecode sont des instructions à adresse nulle, qui reposent sur la pile d'opérandes pour le travail.

Par exemple 1 + 1:

iconst_1
iconst_1
iadd
istore_0

Après que deux instructions iconst_1 poussent successivement deux constantes 1 dans la pile, l'instruction iadd fait apparaître les deux valeurs en haut de la pile, les ajoute, puis remet le résultat en haut de la pile, et enfin istore_0 met la valeur en haut de la pile dans la table des variables locales Dans le 0e emplacement variable

Avantages et inconvénients

  • Le principal avantage du stack-based est qu'il est portable. Avec une architecture de stack, les programmes utilisateur n'utilisent pas directement les registres. Il peut être implémenté par une machine virtuelle pour mettre certaines des données les plus fréquemment consultées (compteur de programme, cache de stack top, etc.) dans des registres pour améliorer les performances . Le code est compact et le compilateur est facile à implémenter
iconst_1
iconst_1
iadd
istore_0

Après que deux instructions iconst_1 poussent successivement deux constantes 1 dans la pile, l'instruction iadd fait apparaître les deux valeurs en haut de la pile, les ajoute, puis remet le résultat en haut de la pile, et enfin istore_0 met la valeur en haut de la pile dans la table des variables locales Dans le 0e emplacement variable

Avantages et inconvénients

  • Le principal avantage du stack-based est qu'il est portable. Avec une architecture de stack, les programmes utilisateur n'utilisent pas directement les registres. Il peut être implémenté par une machine virtuelle pour mettre certaines des données les plus fréquemment consultées (compteur de programme, cache de stack top, etc.) dans des registres pour améliorer les performances . Le code est compact et le compilateur est facile à implémenter
  • L'inconvénient est que la vitesse d'exécution est légèrement plus lente, le nombre d'instructions nécessaires pour exécuter la même fonction est important et un accès fréquent à la pile signifie un accès mémoire fréquent

Je suppose que tu aimes

Origine blog.csdn.net/weixin_42249196/article/details/108253734
conseillé
Classement