web-dev-qa-db-fra.com

Machine virtuelle Java et CLR

Comme une sorte de suivi de la question appelée Différences entre MSIL et Java bytecode? , quelles sont les différences (majeures) ou similitude dans la façon dont le Java Virtual Machine fonctionne par rapport à la façon dont .NET Framework Le Common Language Runtime (CLR) fonctionne?

En outre, le Framework .NET CLR une "machine virtuelle" ou n'a-t-elle pas les attributs d'une machine virtuelle?

135
Frank V

Il existe de nombreuses similitudes entre les deux implémentations (et à mon avis: oui, ce sont toutes les deux des "machines virtuelles").

D'une part, ce sont tous les deux des machines virtuelles basées sur la pile, sans aucune notion de "registres" comme nous en avons l'habitude dans un processeur moderne comme le x86 ou le PowerPC. L'évaluation de toutes les expressions ((1 + 1)/2) est effectuée en poussant les opérandes sur la "pile", puis en sortant ces opérandes de la pile chaque fois qu'une instruction (ajouter, diviser, etc.) doit consommer ces opérandes. Chaque instruction repousse ses résultats sur la pile.

C'est un moyen pratique d'implémenter une machine virtuelle, car à peu près tous les processeurs du monde ont une pile, mais le nombre de registres est souvent différent (et certains registres sont à usage spécial, et chaque instruction attend ses opérandes dans différents registres, etc. ).

Donc, si vous voulez modéliser une machine abstraite, un modèle purement basé sur la pile est une assez bonne façon de procéder.

Bien sûr, les vraies machines ne fonctionnent pas de cette façon. Le compilateur JIT est donc chargé d'effectuer "l'enregistrement" des opérations de bytecode, en planifiant essentiellement les registres CPU réels pour contenir les opérandes et les résultats chaque fois que possible.

Donc, je pense que c'est l'une des plus grandes similitudes entre le CLR et la JVM.

Quant aux différences ...

Une différence intéressante entre les deux implémentations est que le CLR comprend des instructions pour créer des types génériques, puis pour appliquer des spécialisations paramétriques à ces types. Ainsi, lors de l'exécution, le CLR considère un List <int> comme un type complètement différent d'un List <String>.

Sous les couvertures, il utilise le même MSIL pour toutes les spécialisations de type référence (donc une List <String> utilise la même implémentation qu'un List <Object>, avec des transformations de type différentes aux limites de l'API), mais chaque type de valeur utilise sa propre implémentation unique (List <int> génère un code complètement différent de List <double>).

En Java, les types génériques sont purement une astuce de compilation. La JVM n'a aucune idée des classes qui ont des arguments de type et elle n'est pas en mesure d'effectuer des spécialisations paramétriques au moment de l'exécution.

D'un point de vue pratique, cela signifie que vous ne pouvez pas surcharger Java sur les types génériques. Vous ne pouvez pas avoir deux méthodes différentes, avec le même nom, ne différant que sur l'acceptation d'une liste < String> ou List <Date>. Bien sûr, puisque le CLR connaît les types paramétriques, il n'a aucun problème à gérer les méthodes surchargées sur les spécialisations de type générique.

Au jour le jour, c'est la différence que je remarque le plus entre le CLR et la JVM.

D'autres différences importantes comprennent:

  • Le CLR a des fermetures (implémentées en tant que délégués C #). La JVM ne prend en charge les fermetures que depuis Java 8.

  • Le CLR a des coroutines (implémentées avec le mot-clé C # 'yield'). La JVM ne fonctionne pas.

  • Le CLR permet au code utilisateur de définir de nouveaux types de valeurs (structures), tandis que la JVM fournit une collection fixe de types de valeurs (octet, court, int, long, flottant, double, char, booléen) et permet uniquement aux utilisateurs de définir une nouvelle référence - types (classes).

  • Le CLR prend en charge la déclaration et la manipulation de pointeurs. Ceci est particulièrement intéressant car la JVM et le CLR utilisent des implémentations strictes de compactage générationnel de garbage collector comme stratégie de gestion de la mémoire. Dans des circonstances ordinaires, un GC de compactage strict a vraiment du mal avec les pointeurs, car lorsque vous déplacez une valeur d'un emplacement mémoire vers un autre, tous les pointeurs (et les pointeurs vers les pointeurs) deviennent invalides. Mais le CLR fournit un mécanisme de "verrouillage" afin que les développeurs puissent déclarer un bloc de code dans lequel le CLR n'est pas autorisé à déplacer certains pointeurs. C'est très pratique.

  • La plus grande unité de code dans la JVM est soit un "package" comme en témoigne le mot-clé "protected" ou sans doute un JAR (c'est-à-dire Java ARchive) comme en témoigne la possibilité de spécifier un bocal dans le chemin de classe et le traiter comme un dossier de code. Dans le CLR, les classes sont agrégées en "assemblys", et le CLR fournit une logique de raisonnement et de manipulation des assemblys (qui sont chargés dans "AppDomains", fournissant un niveau de sous-application bacs à sable pour l'allocation de mémoire et l'exécution de code).

  • Le format de bytecode CLR (composé d'instructions MSIL et de métadonnées) a moins de types d'instructions que la JVM. Dans la JVM, chaque opération unique (ajoutez deux valeurs int, ajoutez deux valeurs flottantes, etc.) a sa propre instruction unique. Dans le CLR, toutes les instructions MSIL sont polymorphes (ajoutez deux valeurs) et le compilateur JIT est chargé de déterminer les types des opérandes et de créer le code machine approprié. Je ne sais pas quelle est la stratégie de préférence, cependant. Les deux ont des compromis. Le compilateur HotSpot JIT, pour la JVM, peut utiliser un mécanisme de génération de code plus simple (il n'a pas besoin de déterminer les types d'opérandes, car ils sont déjà encodés dans l'instruction), mais cela signifie qu'il a besoin d'un format de bytecode plus complexe, avec plus de types d'instructions.

J'utilise Java (et j'admire la JVM) depuis une dizaine d'années maintenant.

Mais, à mon avis, le CLR est maintenant la mise en œuvre supérieure, dans presque tous les sens.

272
benjismith

Votre première question est de comparer la JVM avec le .NET Framework - je suppose que vous vouliez réellement comparer avec le CLR à la place. Si oui, je pense que vous pourriez écrire un petit livre à ce sujet ( EDIT: on dirait que Benji a déjà :-)

Une différence importante est que le CLR est conçu pour être une architecture indépendante du langage, contrairement à la JVM.

Une autre différence importante est que le CLR a été spécialement conçu pour permettre un haut niveau d'interopérabilité avec le code natif. Cela signifie que le CLR doit gérer la fiabilité et la sécurité lors de l'accès et de la modification de la mémoire native, ainsi que gérer le marshaling entre les structures de données basées sur le CLR et les structures de données natives.

Pour répondre à votre deuxième question, le terme "machine virtuelle" est un terme plus ancien du monde matériel (par exemple, la virtualisation d'IBM du 360 dans les années 1960) qui signifiait auparavant une émulation logicielle/matérielle de la machine sous-jacente pour accomplir le même type de des choses que VMWare fait.

Le CLR est souvent appelé "moteur d'exécution". Dans ce contexte, il s'agit d'une implémentation d'une machine IL au-dessus d'un x86. C'est également ce que fait la JVM, bien que vous puissiez affirmer qu'il existe une différence importante entre les bytecodes polymorphes du CLR et les bytecodes typés de la JVM.

La réponse pédante à votre deuxième question est donc "non". Mais cela se résume vraiment à la façon dont vous définissez ces deux termes.

EDIT: Une autre différence entre la JVM et le CLR est que la JVM (version 6) est très réticente à libérer alloué mémoire au système d'exploitation, même là où il le peut.

Par exemple, supposons qu'un processus JVM démarre et alloue initialement 25 Mo de mémoire à partir du système d'exploitation. Le code d'application tente ensuite des allocations qui nécessitent 50 Mo supplémentaires. La machine virtuelle Java allouera 50 Mo supplémentaires à partir du système d'exploitation. Une fois que le code d'application a cessé d'utiliser cette mémoire, il est récupéré et la taille du segment JVM diminue. Cependant, la JVM ne libérera la mémoire du système d'exploitation allouée que dans certaines circonstances très spécifiques . Sinon, pour le reste de la durée de vie du processus, cette mémoire restera allouée.

Le CLR, d'autre part, libère la mémoire allouée au système d'exploitation si elle n'est plus nécessaire. Dans l'exemple ci-dessus, le CLR aurait libéré la mémoire une fois que le tas avait diminué.

25
RoadWarrior

Plus de détails sur les différences peuvent être trouvés à partir de diverses sources académiques et privées. Un bon exemple est CLR Design Choices .

Quelques exemples spécifiques:

  • Certaines opérandes de bas niveau sont typées telles que "ajouter deux entrées" alors que CLR utilise un opérande polymorphe. (c'est-à-dire fadd/iadd/ladd vs juste ajouter)
  • Actuellement, la JVM fait un profil d'exécution et une optimisation plus agressifs (c'est-à-dire Hotspot). CLR effectue actuellement des optimisations JIT, mais pas d'optimisation d'exécution (c'est-à-dire remplacer le code pendant que vous exécutez).
  • CLR n'inline pas les méthodes virtuelles, JVM le fait ...
  • Prise en charge des types de valeur dans le CLR au-delà des "primitives".
11
James Schek

Le CLR et la JVM sont tous deux des machines virtuelles.

Le .NET Framework et Java Runtime Environment sont le regroupement des VM respectives et de leurs bibliothèques. Sans bibliothèques, les VM sont assez inutiles.

9
Allain Lalonde