JVM --- moteur d'exécution

Moteur d'exécution

Présentation du moteur d'exécution

Insérez la description de l'image ici

  • Le moteur d'exécution est l'un des principaux composants de la machine virtuelle Java.
  • La "machine virtuelle" est un concept relatif à la "machine physique". Les deux machines ont des capacités d'exécution de code. La différence est que le moteur d'exécution de la machine physique est directement construit sur les niveaux du processeur, du cache, du jeu d'instructions et du système d'exploitation., Et le moteur d'exécution de la machine virtuelle est réalisé par le logiciel lui-même, il est donc possible de personnaliser la structure du jeu d'instructions et du moteur d'exécution sans être limité par des conditions physiques, et peut exécuter ces formats de jeu d'instructions qui ne sont pas directement pris en charge par le matériel.
  • La tâche principale de la JVM est d'y charger le bytecode, mais le bytecode ne peut pas s'exécuter directement sur le système d'exploitation, car les instructions bytecode ne sont pas équivalentes aux instructions de l'ordinateur local. Il ne contient que des instructions Bytecode, des tables de symboles et d'autres informations auxiliaires reconnues par le JVM.
  • Si vous souhaitez qu'un programme Java s'exécute, la tâche du moteur d'exécution consiste à interpréter / compiler les instructions de bytecode en instructions machine natives sur la plate-forme correspondante. En termes simples, le moteur d'exécution de la JVM agit comme un traducteur qui traduit un langage de haut niveau en langage machine.
    Insérez la description de l'image ici
  • Compilation frontale: ce processus à partir de fichiers de code programme Java est appelé compilation frontale.
  • Le moteur d'exécution a ici deux comportements: l'un est l'exécution de l'interprétation et l'autre est l'exécution de la compilation (ici, la compilation back-end).

Processus de travail du moteur d'exécution

  1. Les instructions de bytecode que le moteur d'exécution doit exécuter pendant le processus d'exécution dépendent entièrement du registre du PC;
  2. Chaque fois qu'une opération d'instruction est exécutée, le registre PC mettra à jour l'adresse de l'instruction suivante qui doit être exécutée;
  3. Pendant l'exécution du procédé, le moteur d'exécution peut localiser avec précision les informations d'instance d'objet stockées dans la zone de tas Java via la référence d'objet stockée dans la table de variables locales, et localiser l'objet cible via le pointeur de métadonnées dans les informations de type d'en-tête d'objet;
    Insérez la description de l'image ici

Du point de vue de l'apparence, l'entrée, le traitement et la sortie du moteur d'exécution de toutes les machines virtuelles Java sont les mêmes: l'entrée est un flux binaire de bytecode, et le processus de traitement est le processus équivalent d'analyse et d'exécution de bytecode, et juste- compilation en temps réel. C'est le processus d'exécution.

Processus de compilation et d'exécution de code Java

Exécution interprétée et compilation juste à temps

Avant que la majeure partie du code du programme ne soit convertie en code cible de la machine physique ou du jeu d'instructions que la machine virtuelle peut exécuter, elle doit passer par les différentes étapes de la figure suivante:

  1. La partie orange en face est le processus de compilation et de génération de fichiers bytecode (qui est fait par le compilateur javac, c'est-à-dire le compilateur frontal), qui n'a rien à voir avec JVM;
  2. Le vert (exécution de l'interprétation) et le bleu (compilation juste à temps) suivants sont les processus que la JVM doit prendre en compte.
    Insérez la description de l'image ici
    L'organigramme du compilateur javac (compilateur frontal) est le suivant:
    Insérez la description de l'image ici
    L'exécution du bytecode Java est effectuée par le moteur d'exécution JVM. L'organigramme est le suivant:
    Insérez la description de l'image ici
    Utilisez un diagramme général pour parler de l'interpréteur et du compilateur:
    Insérez la description de l'image ici

Qu'est-ce qu'un interprète? Qu'est-ce qu'un compilateur JIT?

  • Interpréteur: Au démarrage de la machine virtuelle Java, le bytecode sera interprété ligne par ligne selon les spécifications prédéfinies, et le contenu de chaque fichier bytecode sera "traduit" dans les instructions de la machine locale de la plateforme correspondante pour exécution.
  • Compilateur JIT (Just In Time Compiler): C'est la machine virtuelle qui compile directement le code source dans le langage machine lié à la plate-forme de la machine locale à un moment donné, mais elle n'est pas exécutée immédiatement.

Pourquoi Java est-il un langage semi-compilé et semi-interprété?

  • A l'ère JDK1.0, il est plus précis de positionner le langage Java comme "exécution interprétée". Plus tard, Java a également développé un compilateur capable de générer directement du code natif.
  • Désormais, lorsque JVM exécute du code Java, il combine généralement l'interprétation et l'exécution avec la compilation et l'exécution.
  • Une fois que le compilateur JIT a traduit le bytecode en code local, il peut effectuer une opération de mise en cache et le stocker dans le cache de code JIT dans la zone de méthode (l'efficacité d'exécution est plus élevée), et il peut être optimisé dans le processus de traduction du code local .

La relation entre le langage de haut niveau, le langage d'assemblage et les instructions machine
Insérez la description de l'image ici

Bytecode

  • Bytecode est un code binaire (fichier) d'un état intermédiaire (code intermédiaire), qui est plus abstrait que le code machine et doit être traduit par un interpréteur pour devenir du code machine;
  • Le bytecode sert principalement à réaliser des opérations logicielles et un environnement logiciel spécifiques, et n'a rien à voir avec l'environnement matériel;
  • L'implémentation du bytecode se fait via un compilateur et une machine virtuelle. Le compilateur compile le code source en bytecode, et la machine virtuelle sur une plate-forme spécifique traduit le bytecode en instructions qui peuvent être directement exécutées;
  • L'application typique du bytecode est: Bytecode Java.

Interprète

Pourquoi y a-t-il un interprète?

  1. L'intention initiale des concepteurs de JVM était purement de satisfaire les fonctionnalités multiplateformes des programmes Java. Par conséquent, afin d'éviter l'utilisation de la compilation statique pour générer directement des instructions machine locales à partir du langage de haut niveau, l'idée d'implémenter le programme en interprétant le bytecode ligne par ligne au moment de l'exécution est née (c'est-à-dire un produit intermédiaire bytecode est généré).
  2. Le véritable rôle de l'interpréteur est un "traducteur" d'exécution, qui "traduit" le contenu du fichier bytecode dans les instructions de la machine locale de la plate-forme correspondante pour l'exécution.
  3. Lorsqu'une instruction de bytecode est interprétée et exécutée, l'opération d'interprétation est effectuée conformément à l'instruction de bytecode suivante à exécuter enregistrée dans le registre PC.
    Insérez la description de l'image ici
    Pourquoi les fichiers source Java ne sont-ils pas directement traduits en code machine, mais en fichiers bytecode? Cela peut être dû au fait que le code directement traduit est relativement volumineux.

Classification des interprètes

  • Dans l'histoire du développement de Java, il existe deux ensembles d'exécuteurs d'interprétation, à savoir l'ancien interpréteur de bytecode et l'interpréteur de modèles couramment utilisés actuellement.

    • L'interpréteur de bytecode simule l'exécution de bytecode via du code logiciel pur pendant l'exécution, ce qui est très inefficace.
    • L'interpréteur de modèle associe chaque code d'octet à une fonction de modèle, et la fonction de modèle génère directement le code machine lorsque le code d'octet est exécuté, améliorant ainsi considérablement les performances de l'interpréteur.
  • Dans HotSpot VM, l'interpréteur est principalement composé du module Interpreter et du module Code.

    • Module d'interprétation: implémente les fonctions de base de l'interpréteur
    • Module de code: utilisé pour gérer les instructions de la machine locale générées par la VM HotSpot lors de l'exécution.

L'état actuel de l'interprète

  1. Parce que l'interpréteur est très simple dans sa conception et sa mise en œuvre, en plus du langage Java, il existe de nombreux langages de haut niveau qui sont également exécutés sur la base de l'interpréteur, tels que Python, Perl, Ruby, etc. Mais aujourd'hui, l'exécution basée sur un interpréteur est devenue synonyme d'inefficacité et est souvent ridiculisée par certains programmeurs C / C ++.
  2. Pour résoudre ce problème, la plate-forme JVM prend en charge une technologie appelée compilation juste à temps. Le but de la compilation juste-à-temps est d'empêcher que la fonction soit interprétée et exécutée, mais de compiler le corps entier de la fonction en code machine. Chaque fois que la fonction est exécutée, seul le code machine compilé est exécuté. Cette méthode peut grandement améliorer l'efficacité de l'exécution.
  3. Cependant, le mode d'exécution basé sur l'interpréteur apporte toujours une contribution indélébile au développement du langage intermédiaire.

Compilateur JIT

Classification de l'exécution du code Java:

  • La première consiste à compiler le code source dans un fichier bytecode, puis à utiliser l'interpréteur pour convertir le fichier bytecode en code machine pour exécution au moment de l'exécution;
  • La seconde consiste à compiler et exécuter (compiler directement dans le code machine). Afin d'améliorer l'efficacité de l'exécution, les machines virtuelles modernes utilisent la technologie Just In Time (JIT) pour compiler les méthodes en code machine avant leur exécution.

HotSpot VM est l'un des chefs-d'œuvre des machines virtuelles hautes performances du marché. Il utilise une architecture dans laquelle un interpréteur et un compilateur juste à temps coexistent. Lorsque la machine virtuelle Java est en cours d'exécution, l'interpréteur et le compilateur juste à temps peuvent coopérer l'un avec l'autre, chacun apprenant des points forts de l'autre et essayant de choisir la manière la plus appropriée de peser le temps de compilation du code natif et du le temps d'interpréter et d'exécuter directement le code.

Pourquoi avons-nous besoin d'un interprète?

  • Lorsque le programme est lancé, l'interpréteur peut prendre effet immédiatement, la vitesse de réponse est rapide, le temps de compilation est enregistré et il est exécuté immédiatement.
  • Pour que le compilateur fonctionne, il faut un certain temps d'exécution pour compiler le code en code local, mais une fois compilé en code natif, l'efficacité d'exécution est élevée.
  • Bien que les performances d'exécution du programme dans la VM JRockit soient très efficaces, la compilation prendra inévitablement plus de temps au démarrage du programme. Pour les applications côté serveur, le temps de démarrage n'est pas au centre de l'attention, mais pour les scénarios d'application qui valorisent le temps de démarrage, il peut être nécessaire d'adopter une architecture où un interpréteur et un compilateur juste à temps coexistent en échange d'un équilibre .
  • Lorsque la machine virtuelle est démarrée, l'interpréteur peut travailler en premier, au lieu d'attendre que le compilateur juste à temps termine la compilation, puis l'exécute, ce qui peut économiser beaucoup de temps de compilation inutile. Avec le passage du temps d'exécution du programme, le compilateur juste-à-temps entre progressivement en jeu. Selon la fonction de détection des points chauds, le bytecode précieux est compilé dans les instructions de la machine locale en échange d'une plus grande efficacité d'exécution du programme.
  • En même temps, l'interprétation et l'exécution servent de «porte de secours» (plan de sauvegarde) du compilateur lorsque l'optimisation radicale du compilateur échoue.

Concepts liés au compilateur JIT

  1. La «période de compilation» du langage Java est en fait une période d'opération «incertaine», car elle peut faire référence à un compilateur frontal (en fait appelé «compilateur frontal» est plus précis) convertissant les fichiers .java en fichiers .class traiter.
  2. Il peut également faire référence au processus par lequel le compilateur d'exécution principal (compilateur JIT, Just In Time Compiler) de la machine virtuelle convertit le bytecode en code machine.
  3. Il peut également faire référence au processus de compilation directe des fichiers .java dans le code de la machine locale à l'aide d'un compilateur statique à l'avance (compilateur AOT, Ahead of Time Compiler). (Peut-être la tendance du développement de suivi)

Compilateur typique:

  • Compilateur frontal: javac de Sun, compilateur incrémental (ECJ) dans Eclipse JDT.
  • Compilateur JIT: compilateur C1 et C2 de HotSpot VM.
  • Compilateur AOT: Compilateur GNU pour Java (GCJ), Excelsior JET.

Code chaud et méthode de détection

  1. Bien entendu, la nécessité de démarrer le compilateur JIT pour compiler directement le bytecode dans les instructions machine natives de la plate-forme correspondante dépend de la fréquence à laquelle le code est appelé et exécuté.
  2. En ce qui concerne le bytecode qui doit être compilé en code natif, également connu sous le nom de "code à chaud", le compilateur JIT optimisera profondément le "code à chaud" fréquemment appelé au moment de l'exécution, et le compilera directement dans Correspondant aux instructions de la machine native de la plate-forme pour améliorer les performances d'exécution des programmes Java.
  3. Une méthode qui est appelée plusieurs fois, ou un corps de boucle avec un grand nombre de boucles dans le corps de la méthode peut être appelé "code actif", afin qu'il puisse être compilé en instructions machine locales par le compilateur JIT. Étant donné que cette méthode de compilation se produit pendant l'exécution de la méthode, elle est également appelée remplacement sur pile, ou compilation OSR (On Stack Replacement) en abrégé.
  4. Combien de fois une méthode doit-elle être appelée, ou combien de fois un corps de boucle doit-il s'exécuter pour atteindre ce standard? Un seuil clair doit être requis pour que le compilateur JIT compile ces "codes à chaud" dans les instructions de la machine locale pour exécution. Cela dépend principalement de la fonction de détection des points chauds.
  5. La méthode de détection des points chauds actuellement utilisée par HotSpot VM est la détection des points chauds basée sur un compteur.
  6. En utilisant la détection des points chauds basée sur les compteurs, HotSpot VM créera deux types de compteurs différents pour chaque méthode, à savoir le compteur d'invocation et le compteur d'arrière-plan.
  • Le compteur d'appels de méthode est utilisé pour compter le nombre d'appels de méthode.
  • Le compteur de retour de front est utilisé pour compter le nombre de boucles exécutées par le corps de la boucle.

Compteur d'appels de méthode

  • Ce compteur permet de compter le nombre d'appels de la méthode. Son seuil par défaut est de 1 500 fois en mode Client et 10 000 fois en mode Serveur. Si ce seuil est dépassé, la compilation JIT sera déclenchée.

  • Ce seuil peut être défini manuellement via le paramètre de machine virtuelle -XX: CompileThreshold.

  • Lorsqu'une méthode est appelée, elle vérifie d'abord s'il existe une version compilée JIT de la méthode.

    • S'il existe, le code natif compilé sera utilisé en premier pour l'exécution;
    • S'il n'y a pas de version compilée, ajoutez 1 à la valeur du compteur d'appels de cette méthode, puis déterminez si la somme du compteur d'appels de méthode et de la valeur du compteur de retour dépasse le seuil du compteur d'appels de méthode;
    • Si le seuil est dépassé, une demande de compilation de code pour cette méthode sera soumise au compilateur juste à temps;
    • Si le seuil n'est pas dépassé, un interpréteur est utilisé pour interpréter et exécuter le fichier bytecode.

Insérez la description de l'image ici

Décomposition thermique

  1. Si rien n'est défini, le compteur d'appels de méthode ne compte pas le nombre absolu d'appels de méthode, mais une fréquence d'exécution relative, c'est-à-dire le nombre d'appels de méthode dans une période de temps. Lorsqu'un certain délai est dépassé, si le nombre d'appels de méthode n'est toujours pas suffisant pour être soumis au compilateur juste à temps pour compilation, le compteur d'appels de cette méthode sera réduit de moitié. Ce processus est appelé la décroissance du compteur du compteur d'appel de méthode., Et cette période de temps est appelée Counter Half LifeTime (Counter Half LifeTime) de cette méthode (la période de demi-vie est un concept en chimie, par exemple, l'âge des reliques culturelles déterrées peut être obtenu en vérifiant C60).
  2. L'action de la décroissance thermique est effectuée de la même manière que la machine virtuelle est récupérée. Vous pouvez utiliser le paramètre de machine virtuelle -XX: -UseCounterDecay pour désactiver la décroissance thermique et laisser le compteur de méthode compter le nombre absolu d'appels de méthode. cas, tant que le système fonctionne pendant assez longtemps, la plupart des méthodes seront compilées en code local.
  3. En outre, vous pouvez utiliser le paramètre -XX: CounterHalfLifeTime pour définir la durée du cycle de demi-vie, en secondes.

Compteur dos à bord

Sa fonction est de compter le nombre d'exécutions du code du corps de la boucle dans une méthode, et l'instruction selon laquelle le flux de contrôle saute en arrière dans le bytecode est appelée le "bord arrière". De toute évidence, le but de l'établissement des statistiques de compteur arrière est de déclencher la compilation OSR.

Insérez la description de l'image ici

HotSpot VM peut définir la méthode d'exécution du programme

Par défaut, la VM HotSpot adopte une architecture dans laquelle un interpréteur et un compilateur juste à temps coexistent. Bien sûr, les développeurs peuvent spécifier explicitement si la machine virtuelle Java est exécutée par l'interpréteur au moment de l'exécution ou non selon des scénarios d'application spécifiques. Il est entièrement exécuté avec un compilateur juste à temps. Comme suit:

  1. -Xint: exécute complètement le programme en mode interpréteur;
  2. -Xcomp: Le programme est exécuté en mode de compilation juste-à-temps complet. S'il y a un problème avec la compilation juste à temps, l'interpréteur interviendra dans l'exécution;
  3. -Xmixed: Adoptez un mode mixte d'interpréteur + compilateur juste à temps pour exécuter des programmes ensemble.
    Insérez la description de l'image ici
    Test de code
/**
 * 测试解释器模式和JIT编译模式
 *  -Xint  : 花费的时间为:4585ms
 *  -Xcomp : 花费的时间为:871ms
 *  -Xmixed : 花费的时间为:867ms
 */
public class IntCompTest {
    
    
    public static void main(String[] args) {
    
    

        long start = System.currentTimeMillis();

        testPrimeNumber(1000000);

        long end = System.currentTimeMillis();

        System.out.println("花费的时间为:" + (end - start));

    }

    public static void testPrimeNumber(int count){
    
    
        for (int i = 0; i < count; i++) {
    
    
            //计算100以内的质数
            label:for(int j = 2;j <= 100;j++){
    
    
                for(int k = 2;k <= Math.sqrt(j);k++){
    
    
                    if(j % k == 0){
    
    
                        continue label;
                    }
                }
                //System.out.println(j);
            }

        }
    }
}


Conclusion: il est vraiment lent à exécuter uniquement avec un interprète!

Classification HotSpot VM JIT

Il existe deux compilateurs JIT intégrés dans HotSpot VM, à savoir Client Compiler et Server Compiler, mais dans la plupart des cas, nous les appelons compilateur C1 et compilateur C2 pour faire court. Les développeurs peuvent spécifier explicitement le type de compilateur juste à temps que la machine virtuelle Java utilise au moment de l'exécution via la commande suivante, comme indiqué ci-dessous:

  1. -client: spécifiez la machine virtuelle Java à exécuter en mode client et utilisez le compilateur C1. Le compilateur C1 effectuera des optimisations simples et fiables sur le bytecode, ce qui prend peu de temps pour atteindre une vitesse de compilation plus rapide.
  2. -server: spécifiez la machine virtuelle Java à exécuter en mode serveur et utilisez le compilateur C2. C2 effectue des optimisations chronophages et des optimisations radicales, mais l'efficacité d'exécution de code optimisée est plus élevée. (Écrit en C ++)

Différentes stratégies d'optimisation pour les compilateurs C1 et C2

  1. Il existe différentes stratégies d'optimisation sur différents compilateurs. Sur le compilateur C1, il existe principalement des méthodes d'inlining, de dé-virtualisation et d'élimination des surplus.
  • Inlining de méthode: compilez le code de fonction référencé au point de référence, ce qui peut réduire la génération de cadres de pile, réduire le passage de paramètres et les processus de saut.
  • Dévirtualisation: Inline la seule classe d'implémentation.
  • Élimination de la redondance: dépliez du code qui ne sera pas exécuté pendant le fonctionnement.
  1. L'optimisation de C2 se fait principalement au niveau global, et l'analyse des échappements est la base de l'optimisation. Il existe plusieurs optimisations sur C2 basées sur l'analyse d'échappement:
  • Remplacement scalaire: remplacez la valeur d'attribut de l'objet agrégé par une valeur scalaire.
  • Allocation sur la pile: pour les objets qui n'ont pas échappé, les objets sont alloués sur la pile au lieu du tas.
  • Élimination de la synchronisation: opération de synchronisation claire, généralement appelée synchronisée.

C'est-à-dire que l'analyse d'échappement précédente ne sera déclenchée qu'en C2 (mode serveur). Cela signifie-t-il que C1 ne peut plus être utilisé?

Stratégie de compilation stratifiée

  1. Stratégie de compilation à plusieurs niveaux: l'interprétation et l'exécution du programme (sans surveillance des performances activée) peuvent déclencher la compilation C1 et compiler le bytecode en code machine. Une optimisation simple peut être effectuée ou une surveillance des performances peut être ajoutée. La compilation C2 sera basée sur la surveillance des performances Les informations sont radicalement optimisées .
  2. Cependant, après la version Java7, une fois que le développeur spécifie explicitement la commande «-server» dans le programme, la stratégie de compilation hiérarchique sera activée par défaut, et le compilateur C1 et le compilateur C2 coopéreront entre eux pour effectuer des tâches de compilation.

D'une manière générale, les performances du code machine compilé par JIT sont supérieures à celles de l'interpréteur. Le temps de démarrage du compilateur C2 est plus lent que celui de C1. Une fois le système exécuté de manière stable, la vitesse d'exécution du compilateur C2 est beaucoup plus rapide que celui du compilateur C1.

Compilateur Graal

  1. Depuis JDK10, HotSpot a ajouté un nouveau compilateur juste à temps: le compilateur Graal;
  2. L'effet de compilation a été égal au compilateur C2 en quelques années seulement, et l'avenir est prévisible (correspondant à la machine virtuelle Graal, il est possible de remplacer la machine virtuelle Hotspot);
  3. À l'heure actuelle, avec l'étiquette d'état de l'expérience, vous devez utiliser le paramètre switch pour l'activer avant de pouvoir l'utiliser.
-XX:+UnlockExperimentalvMOptions -XX:+UseJVMCICompiler

Compilateur AOT

  1. jdk9 a introduit le compilateur AOT (compilateur statique à l'avance, Ahead of Time Compiler);
  2. Java 9 a introduit l'outil de compilation expérimental AOT jaotc. Il utilise le compilateur Graal pour convertir les fichiers de classe Java d'entrée en code machine et les stocker dans la bibliothèque partagée dynamique générée.
  3. La soi-disant compilation AOT est un concept opposé à la compilation juste à temps. La compilation juste à temps fait référence au processus de conversion du bytecode en code machine qui peut être directement exécuté sur le matériel pendant l'exécution du programme et son déploiement dans l'environnement d'hébergement. La compilation AOT fait référence au processus de conversion du bytecode en code machine avant l'exécution du programme.
.java -> .class -> (使用jaotc) -> .so

Avantages et inconvénients du compilateur AOT

Le plus grand avantage:

  1. Le chargement de la machine virtuelle Java a été pré-compilé dans une bibliothèque binaire et peut être exécuté directement;
  2. Il n'est pas nécessaire d'attendre le préchauffage du compilateur juste à temps pour réduire la mauvaise expérience de "première exécution lente" pour les applications Java.

Désavantages:

  1. Pour détruire Java "compilez une fois, exécutez partout", le package de version correspondant doit être compilé pour chaque matériel et système d'exploitation différents;
  2. Réduisez la dynamique du processus de liaison Java, le code chargé doit être connu dans le compilateur;
  3. Encore faut-il continuer à optimiser, initialement ne prend en charge que la base java Linux X64.

Je suppose que tu aimes

Origine blog.csdn.net/qq_33626996/article/details/114706674
conseillé
Classement