Article de reference

machine virtuelle Java

{{cite web |title=JEP 479: Remove the Windows 32-bit x86 Port |url=https://openjdk.org/jeps/479 |website=OpenJDK}} and JDK 25 (Linux). {{cite web |title=JEP 503: Remove the 32-b...

Présentation de l'architecture d'une machine virtuelle Java (JVM) basée sur la spécification Java SE 7.

Une machine virtuelle Java ( JVM ) permet à un ordinateur d'exécuter des programmes Java , ainsi que des programmes écrits dans d'autres langages, appelés langages JVM , qui sont également compilés en bytecode Java . La JVM est décrite en détail par une spécification qui définit formellement les exigences de son implémentation. Cette spécification garantit l'interopérabilité des programmes Java entre différentes implémentations, permettant ainsi aux développeurs utilisant le kit de développement Java (JDK) de ne pas se soucier des spécificités de la plateforme matérielle sous-jacente.

L' implémentation de référence de la JVM est développée par le projet OpenJDK en tant que code source ouvert et inclut un compilateur JIT appelé HotSpot . Les versions commerciales de Java distribuées par Oracle sont basées sur l'environnement d'exécution OpenJDK. Eclipse OpenJ9 est une autre JVM open source pour OpenJDK.

environnement d'exécution Java . L' algorithme de ramasse-miettes utilisé et toute optimisation interne des instructions de la machine virtuelle Java (leur traduction en code machine ) ne sont pas spécifiés. La principale raison de cette omission est de ne pas contraindre inutilement les développeurs. Toute application Java ne peut être exécutée que dans une implémentation concrète de la spécification abstraite de la machine virtuelle Java.

À partir de Java Platform, Standard Edition (J2SE) 5.0, les modifications apportées à la spécification JVM ont été développées dans le cadre du processus communautaire Java sous la référence JSR 924. format des fichiers de classe (JSR 202) sont effectuées dans le cadre d'une version de maintenance de JSR 924. La spécification de la JVM a été publiée sous le nom de livre bleu , dont la préface indique :

Cette spécification a pour objectif de documenter suffisamment la machine virtuelle Java pour permettre des implémentations compatibles en environnement propre. Oracle fournit des tests qui vérifient le bon fonctionnement des implémentations de la machine virtuelle Java.

La machine virtuelle Java la plus couramment utilisée est HotSpot d'Oracle .

Oracle est propriétaire de la marque Java et peut autoriser son utilisation pour certifier que les suites d'implémentation sont entièrement compatibles avec la spécification d'Oracle.

Éboueurs

Java OpenJDK incluent :

  • En série
  • Parallèle
  • CMS (Balayage simultané des marques)
  • G1 (Déchets en premier)
  • ZGC (Z Garbage Collector)
  • Epsilon
  • Shenandoah
  • GenZGC (ZGC générationnel)
  • GenShen (Generational Shenandoah)
  • IBM Metronome (uniquement dans IBM OpenJDK)
  • SAP (uniquement dans SAP OpenJDK)
  • Azul C4 (Collecteur de compactage continu) (uniquement dans Azul Systems OpenJDK)
Versions de Java et leurs ramasse-miettes
VersionGC par défautCartes-cadeaux disponibles
6u14Série / Parallèle ( MP )Série, parallèle, CMS , G1 (E)
7u4–8Série, parallèle, CMS, G1
9–10G1
11Série, parallèle, CMS, G1, Epsilon (E) , ZGC (E)
12–13Série, Parallèle, CMS, G1, Epsilon (E) , ZGC (E) , Shenandoah (E)
14Série, Parallèle, G1, Epsilon (E) , ZGC (E) , Shenandoah (E)
15–20Série, Parallèle, G1, Epsilon (E) , ZGC, Shenandoah
21–22Série, parallèle, G1, Epsilon (E) , ZGC, Shenandoah, GenZGC (E)
23Série, Parallèle, G1, Epsilon (E) , ZGC, Shenandoah, GenZGC (ZGC par défaut)
24Série, Parallèle, G1, Epsilon (E) , Shenandoah, GenZGC, GenShen (E)
25Série, Parallèle, G1, Epsilon (E) , Shenandoah, GenZGC, GenShen
(E) = expérimental

La philosophie de conception de Java repose sur l'hypothèse d'un ramasse-miettes. Contrairement à des langages comme C++ et Rust , la gestion déterministe de la mémoire via un deletemot-clé (comme en C++) est impossible. Même l'introduction d'une telle fonctionnalité est impossible, faute de droits d'accès, hormis l'utilisation de `sun.misc.Unsafe` ou de `java.lang.foreign` pour allouer/libérer de la mémoire en dehors du tas Java. Étant donné que Java utilise principalement l'allocation sur le tas, les objets sont stockés sous forme de références, et leur suppression entraînerait la création de pointeurs non valides .

entiers et nombres à virgule flottante ) et en types référence . longLes doubletypes `int` et `float`, codés sur 64 bits , sont pris en charge nativement, mais occupent deux unités de stockage dans les variables locales ou la pile d'opérandes d'une trame, chaque unité étant de 32 bits. Les types `int`, `float` boolean, byte` shortfloat` et char`float` sont tous étendus au signe (sauf ` charfloat` , étendu à zéro ) et traités comme des entiers 32 bits, de la même manière que intles types `int`. Les types plus petits ne disposent que de quelques instructions spécifiques pour le chargement, le stockage et la conversion de type. `float` booleanest traité comme byteune valeur 8 bits, 0 représentant `true` falseet 1 représentant `false` true. (Bien que ` booleanT` soit considéré comme un type depuis que la spécification de la machine virtuelle Java, deuxième édition, a clarifié ce point, dans le code compilé et exécuté, il y a peu de différence entre `T` booleanet `T`, bytehormis la modification des noms dans les signatures de méthodes et le type des tableaux booléens. booleanLes `T` dans les signatures de méthodes sont modifiés en `T`, Ztandis que byteles `T` sont modifiés en `T` B. Les tableaux booléens ont le type ` boolean[]T`, mais utilisent 8 bits par élément, et la JVM ne dispose pas de fonctionnalité intégrée pour encapsuler les booléens dans un tableau de bits . Par conséquent, mis à part le type, ils se comportent comme bytedes tableaux. Dans tous les autres cas, le booleantype `T` est effectivement inconnu de la JVM, car toutes les instructions permettant d'opérer sur les booléens sont également utilisées pour opérer sur byteles `T`.)

La JVM utilise un tas (ou ramasse-miettes) pour stocker les objets et les tableaux. Le code, les constantes et les autres données de classe sont stockés dans la zone de méthodes. Logiquement, cette zone fait partie du tas, mais les implémentations peuvent la traiter séparément et, par exemple, ne pas la faire collecter par le ramasse-miettes. Chaque thread JVM possède également sa propre pile d'appels (appelée « pile de la machine virtuelle Java » pour plus de clarté), qui stocke des cadres d' exécution. Un nouveau cadre est créé à chaque appel de méthode et détruit à la fin de l'exécution de cette méthode.

Chaque frame fournit une « pile d'opérandes » et un tableau de « variables locales ». La pile d'opérandes sert à exécuter des calculs sur les opérandes et à recevoir la valeur de retour d'une méthode appelée, tandis que les variables locales ont la même fonction que les registres et servent également à passer les arguments des méthodes. Ainsi, la JVM est à la fois une machine à pile et une machine à registres . En pratique, HotSpot élimine toute pile autre que la pile d'appels/de threads native, même en mode interprété, car son interpréteur de modèles fonctionne techniquement comme un compilateur.

La JVM utilise des références et des index de pile/tableau pour adresser les données ; contrairement à la plupart des machines physiques, elle n'utilise pas l'adressage par octet et ne correspond donc pas à la catégorisation habituelle des machines 32 bits ou 64 bits . D'une certaine manière, on pourrait la classer comme une machine 32 bits, puisque c'est la taille de la plus grande valeur qu'elle stocke nativement : un entier ou un nombre à virgule flottante de 32 bits, ou une référence de 32 bits. Une référence étant codée sur 32 bits, chaque programme est limité à 2<sup> l'utilisation d'une implémentation 64 bits peut engendrer une perte de performances par rapport à une implémentation 32 bits.

Langages JVM

bytecode JVM ), une table des symboles et d'autres informations auxiliaires. Le format de fichier de classe est un format binaire indépendant du matériel et du système d'exploitation, utilisé pour représenter les classes et interfaces compilées.

Il existe plusieurs langages JVM, qu'il s'agisse de langages anciens portés sur la JVM ou de langages plus récents, créés de toutes pièces. JRuby et Jython sont sans doute les portages les plus connus de langages plus anciens, respectivement Ruby et Python . Parmi les nouveaux langages conçus pour compiler en bytecode Java, Clojure , Groovy , Scala et Kotlin sont probablement les plus populaires. Une caractéristique notable des langages JVM est leur interopérabilité : ils sont compatibles entre eux, de sorte que, par exemple, les bibliothèques Scala peuvent être utilisées avec des programmes Java et inversement.

La JVM de Java 7 implémente la JSR 292 : Prise en charge des langages à typage dynamique sur la plateforme Java, une nouvelle fonctionnalité qui permet d’exécuter des langages à typage dynamique au sein de la JVM. Cette fonctionnalité est développée dans le cadre du projet Da Vinci Machine , dont la mission est d’étendre la JVM afin qu’elle prenne en charge des langages autres que Java.

Chargeur de classes

classe . Un chargeur de classes doit pouvoir reconnaître et charger tout élément conforme au format de fichier de classe Java . Toute implémentation peut reconnaître d'autres formats binaires que les fichiers de classe , mais elle doit impérativement reconnaître ces derniers.

Le chargeur de classes effectue trois activités de base dans cet ordre strict :

  1. Chargement : recherche et importe les données binaires pour un type
  2. Liaison : effectue la vérification, la préparation et (éventuellement) la résolution
    • Vérification : garantit l'exactitude du type importé
    • Préparation : alloue de la mémoire pour les variables de classe et initialise cette mémoire à ses valeurs par défaut.
    • Résolution : transforme les références symboliques du type en références directes.
  3. Initialisation : appelle le code Java qui initialise les variables de classe à leurs valeurs initiales appropriées.

En général, il existe trois types de chargeurs de classes : chargeur de classes d’amorçage, chargeur de classes d’extension et chargeur de classes système/application.

Chaque implémentation de machine virtuelle Java doit comporter un chargeur de classes d'amorçage capable de charger les classes de confiance, ainsi qu'un chargeur de classes d'extension ou un chargeur de classes d'application. La spécification de la machine virtuelle Java ne précise pas comment un chargeur de classes doit localiser les classes.

Instructions en bytecode

des instructions pour les groupes de tâches suivants :

Charger et stocker
  • Arithmétique
  • Conversion de type
  • Création et manipulation d'objets
  • Gestion de la pile d'opérandes (push / pop)
  • Transfert de contrôle (ramification)
  • Appel de méthode et retour
  • Lever des exceptions
  • Concurrence basée sur la surveillance
  • L'objectif est la compatibilité binaire. Chaque système d'exploitation hôte nécessite sa propre implémentation de la JVM et de son environnement d'exécution. Ces JVM interprètent le bytecode de la même manière sur le plan sémantique, mais leur implémentation concrète peut différer. L'implémentation compatible et efficace de l' API Java de base , qui doit être adaptée à chaque système d'exploitation hôte, est plus complexe que la simple émulation du bytecode .

    Ces instructions fonctionnent sur un ensemble de paramètres communstypes de données abstraits plutôt que les types de données natifs d'une architecture de jeu d'instructions spécifique.

    vérificateur de bytecode

    L'un des principes fondamentaux de Java est sa sécurité intrinsèque : aucun programme utilisateur ne peut provoquer un plantage de la machine hôte ni interférer de manière inappropriée avec son fonctionnement. De plus, il est possible de protéger certaines méthodes et structures de données appartenant à du code de confiance contre tout accès ou corruption par du code non fiable exécuté au sein de la même JVM. Par ailleurs, les erreurs de programmation courantes, souvent à l'origine de corruptions de données ou de comportements imprévisibles (comme l'accès à la fin d'un tableau ou l'utilisation d'un pointeur non initialisé), sont proscrites. Plusieurs fonctionnalités de Java contribuent à cette sécurité, notamment le modèle de classes, le tas (ou ramasse-miettes ) et le vérificateur.

    La JVM vérifie tout le bytecode avant son exécution. Cette vérification consiste principalement en trois types de contrôles :

    • Les succursales sont toujours à des emplacements valides
    • Les données sont toujours initialisées et les références sont toujours typées.
    • L'accès aux données et méthodes privées ou liées à un package est strictement contrôlé.

    Les deux premiers contrôles ont lieu principalement lors de l'étape de vérification qui se produit lorsqu'une classe est chargée et rendue utilisable. Le troisième est principalement effectué de manière dynamique, lors du premier accès aux données ou aux méthodes d'une classe par une autre classe.

    Le vérificateur n'autorise que certaines séquences de bytecode dans les programmes valides ; par exemple, une instruction de saut (branchement) ne peut cibler qu'une instruction appartenant à la même méthode . De plus, le vérificateur garantit que chaque instruction opère sur une adresse de pile fixe , permettant ainsi au compilateur JIT de transformer les accès à la pile en accès à des registres fixes. Par conséquent, le fait que la JVM soit une architecture à pile n'implique aucune pénalité de vitesse pour l'émulation sur des architectures à registres lors de l'utilisation d'un compilateur JIT. Face à l'architecture JVM à code vérifié, il importe peu au compilateur JIT de recevoir des registres imaginaires nommés ou des adresses de pile imaginaires à allouer aux registres de l'architecture cible. En réalité, la vérification du code distingue la JVM d'une architecture à pile classique, dont l'émulation efficace avec un compilateur JIT est plus complexe et généralement réalisée par un interpréteur plus lent. De plus, l'interpréteur utilisé par la JVM par défaut est un type particulier appelé interpréteur de modèles, qui traduit directement le bytecode en langage machine natif à registres, au lieu d'émuler une pile comme un interpréteur classique. À bien des égards, l'interpréteur HotSpot peut être considéré comme un compilateur JIT plutôt que comme un véritable interpréteur ; autrement dit, l'architecture de pile ciblée par le bytecode n'est pas réellement utilisée dans l'implémentation, mais constitue simplement une spécification de la représentation intermédiaire, qui peut parfaitement être implémentée dans une architecture à registres. Le Common Language Runtime (CLR) est un autre exemple d'architecture de pile qui n'est qu'une spécification et qui est implémentée dans une machine virtuelle à registres .

    La spécification originale du vérificateur de bytecode utilisait un langage naturel incomplet, voire inexact à certains égards. Plusieurs tentatives ont été faites pour formaliser la JVM. Cette formalisation permet d'analyser plus en profondeur la sécurité des implémentations actuelles de la JVM et de prévenir les failles de sécurité potentielles. Il sera également possible d'optimiser la JVM en ignorant les contrôles de sécurité inutiles, si l'application exécutée est jugée sûre.

    Exécution sécurisée de code distant

    L'architecture d'une machine virtuelle permet un contrôle très précis des actions autorisées pour le code exécuté au sein de cette machine. Elle présuppose que le code est sémantiquement correct, c'est-à-dire qu'il a passé avec succès la vérification formelle du bytecode, réalisée par un outil, éventuellement externe à la machine virtuelle. Ce système est conçu pour permettre l'exécution sécurisée de code non fiable provenant de sources distantes, un modèle utilisé par les applets Java et d'autres téléchargements de code sécurisés. Une fois le bytecode vérifié, le code téléchargé s'exécute dans un environnement isolé (« sandbox »), conçu pour protéger l'utilisateur contre les comportements malveillants ou le code défectueux. En complément de la vérification du bytecode, les éditeurs peuvent acquérir un certificat permettant de signer numériquement les applets comme étant sûres, les autorisant ainsi à demander à l'utilisateur de sortir de l'environnement isolé et d'accéder au système de fichiers local, au presse-papiers , d'exécuter des logiciels externes ou au réseau.

    La preuve formelle des vérificateurs de bytecode a été réalisée par l'industrie Javacard (Développement formel d'un vérificateur embarqué pour le bytecode Java Card ).

    Interpréteur de bytecode et compilateur JIT

    Chaque architecture matérielle requiert un interpréteur de bytecode Java différent . Un ordinateur doté d'un tel interpréteur peut exécuter n'importe quel programme en bytecode Java, et ce même programme peut être exécuté sur tout ordinateur équipé d'un interpréteur similaire.

    Lorsqu'un interpréteur exécute du bytecode Java, l'exécution est toujours plus lente que celle du même programme compilé en langage machine natif. Ce problème est atténué par les compilateurs JIT (Just-In-Time) pour l'exécution du bytecode Java. Un compilateur JIT peut traduire le bytecode Java en langage machine natif pendant l'exécution du programme. Les parties traduites du programme peuvent alors être exécutées beaucoup plus rapidement que si elles étaient interprétées. Cette technique est appliquée aux parties du programme fréquemment exécutées. De cette manière, un compilateur JIT peut considérablement accélérer le temps d'exécution global.

    Il n'existe aucun lien nécessaire entre le langage de programmation Java et le bytecode Java. Un programme écrit en Java peut être compilé directement en langage machine sur un ordinateur, et des programmes écrits dans d'autres langages que Java peuvent être compilés en bytecode Java.

    Le bytecode Java est conçu pour être indépendant de la plateforme et sécurisé. Certaines implémentations de la JVM ne comprennent pas d'interpréteur, mais se composent uniquement d'un compilateur à la volée.

    Interface native Java

    framework de programmation d'interface de fonction étrangère (non-Java) qui permet au code Java exécuté dans une machine virtuelle Java (JVM) d'appeler et d'être appelé par des applications natives (programmes spécifiques à une plateforme matérielle et de système d'exploitation ) et des bibliothèques écrites dans d'autres langages tels que C , C++ et assembleur .

    Java 22 introduit l'API Foreign Function and Memory, qui peut être considérée comme la successeure de l'interface native Java.

    Les méthodes natives sont activées par JNI pour gérer les situations où une application ne peut pas être entièrement écrite en langage de programmation Java, par exemple lorsque la bibliothèque de classes Java standard ne prend pas en charge les fonctionnalités spécifiques à la plateforme ou la bibliothèque de programmes.

    Le framework JNI permet à une méthode native d'utiliser des objets Java de la même manière que le code Java. Une méthode native peut créer des objets Java, puis les examiner et les utiliser pour exécuter ses tâches. Elle peut également examiner et utiliser des objets créés par le code d'une application Java.

    JNI permet également un accès direct au code assembleur, sans même passer par un pont C.[3] L'accès aux applications Java depuis l'assembleur est possible de la même manière.

    JVM dans le navigateur web

    Au début de son existence, la plateforme Java (JVM) était commercialisée comme une technologie web permettant de créer des applications web riches . module d'extension Java et n'autorisent pas non plus l'installation de modules d'extension autres que Flash . Le module d'extension Java pour navigateur a été déprécié dans JDK 9.

    Le module d'extension NPAPI pour navigateur Java permet à la JVM d'exécuter des applets Java intégrées aux pages HTML. Sur les navigateurs disposant de ce module, l'applet peut s'afficher dans une zone rectangulaire de la page qui lui est attribuée. Grâce à la JVM intégrée, les applets Java ne sont pas limitées au langage Java ; tout langage ciblant la JVM peut s'exécuter dans le module. Un ensemble restreint d'API permet aux applets d'accéder au microphone de l'utilisateur ou à l'accélération 3D, mais elles ne peuvent pas modifier la page en dehors de leur zone rectangulaire. Adobe Flash Player , principal concurrent, fonctionne de la même manière.

    de Silverlight est tombée à 0,1 % chacune pour tous les sites Web, tandis que celle de Flash est tombée à 10,8 %.

    JVM et interpréteurs JavaScript

    Depuis mai 2016, JavaPoly permet aux utilisateurs d'importer des bibliothèques Java non modifiées et de les appeler directement depuis JavaScript. JavaPoly permet aux sites web d'utiliser des bibliothèques Java non modifiées, même si l'utilisateur n'a pas Java installé sur son ordinateur.

    Transpilation en JavaScript

    Grâce aux progrès constants en matière de vitesse d'exécution JavaScript et à l'utilisation croissante d'appareils mobiles dont les navigateurs web ne prennent pas en charge les plugins, des efforts sont déployés pour cibler ces utilisateurs via la transpilation en JavaScript. Il est possible de transpiler soit le code source, soit le bytecode JVM en JavaScript.

    La compilation du bytecode JVM, universel pour tous les langages JVM, permet de s'appuyer sur le compilateur existant du langage pour générer du bytecode. Les principaux transpileurs de bytecode JVM vers JavaScript sont TeaVM , le compilateur inclus dans le SDK Web Dragome , Bck2Brwsr et j2js-compiler

    Les principaux transpileurs des langages JVM vers JavaScript incluent le transpileur Java vers JavaScript contenu dans Google Web Toolkit , J2CL, Clojurescript (Clojure), GrooScript (Apache Groovy), Scala.js (Scala) et d'autres.