Comparaison machines virtuelles Java/C#
Comparaison des caractéristiques de VMs
Cette page présente succinctement l'historique des langages Java et C# de 1995 jusqu'à 2014. Nous détaillerons les fonctionnalités dans les parties suivantes
Compilation d'un programme et execution
La compilation et l'execution des programmes est similaire en Java et en C#
- Le code source (Java ou C#) est compilé par un compilateur en un code intérmédiaire.
- Ce code intérmédiaire peut ensuite être exécuté sur une VM dépendant du système sur lequel on veut l'executer.

On voit que la compilation et l'execution est similaire en Java et en C# tout comme la compilation "Just In Time" que nous allons voir:
L'execution "Just In Time" (JIT)
Comme on vient de le voir la compilation s'effectue de la même façon en C# et en Java.
Tout comme l'execution qui se passe de la même façon dans les deux langages
Les VM utilisent la compilation "Just In Time" pendant l'execution.
Au fur et à mesure que la machine virtuelle interprète le code elle le compile en langage machine (Code natif), c'est la fonction du JIT Compiler.
Le code ainsi compilé est gardé en mémoire afin d'être executé plus rapidement lors de la prochaine passe.
Tout le code n'est pas compilé à la première passe (Les blocks de code qui ne sont pas exécuté ne sont pas compilés)

Les Code Intermediaire
Prenons l'exemple simple suivant composé d'une classe BasicProgram contenant une variable entière value, de son accesseur et d'un main créant notre classe principale et modifiant la valeur value.

Ce code sera compilé en langage intermédiaire puis désassembler afin de pouvoir visualiser ce code généré
On voit à gauche le code IL C# et a droite le ByteCode Java

On voit que le code intermediaire C# est un peu plus verbeux que le code intermediaire Java.
Cela vient du fait que la machine virtuelle C# contient plus d'instruction que la machine virtuelle Java.
- Le code intermediaire C# est donc plus lourd mais demande moins de traitement à sa machine virtuelle
- La machine virtuelle Java travail plus pour interpeter son code intermediaire mais le lit plus rapidement.
Nous allons voir que ces choix fait par les designer des VM sur le code intermediaire ont en fait un interet lors l'optimisation et de l'execution: Le ByteCode Java est moins verbeux permettant une lecture plus rapide qu'avec l'IL du C#, mais le CLR préfère compiler le code dès la première passe
Les Optimisation et profiling
La JVM tout comme le CLR optimisent le code qu'elle execute mais leurs techniques d'optimisations sont toutefois différentes:
- JVM et les HotSpot: L'éxecution du code par la JVM repose principalement sur l'interprétation du code intermediaire:
- La JVM interprète le code
- Si la JVM détècte qu'une méthode ou qu'une partie du code est appelée régulièrement elle est compilée en langage machine
- Si la JVM détècte que la méthode va être rapellée de nombreuse fois une compilation plus lente mais plus optimisée s'opère
- CLR et les JIT Strategy: au contraire des developpeurs Oracle et de leurs JVM, les développeurs C# ne voulais pas créer un interpreteur optimisé c'est pourquoi le CLR préfère compiler le code dès la première passe ce qui explique sa verbosité.
- Le code est compilé en code natif dès sa première execution par un JIT rapide qui n'effectue aucune optimisation
- Si le code est executé de nombreuses fois un JIT plus lent compile le code en l'optimisant
une JVM server passera plus de temps à optimiser le code qu'une JVM client. (Le programme serveur ne devant jamais s'arreter ce temps sera gagné sur le reste de l'execution)
Les Types primitifs et les Types génériques
- Les types primitifs et la classe Object:
- Java: les types primitifs sont des types particuliers qui n'héritent pas du type Object, par exemple on ne peut pas faire:
Pour réaliser des opération qui nécéssite des Object mais qu'on souhaiterais leur donner un type primitif il faut encapsuler dans un sur-type: Integer pour les int, Double pour les double, ...
C'est pour cela qu'on doit faire des List<Integer> au lieu de List<int> par exemple - C#: tous les types héritent de Object et possède donc les méthodes associés a ceux-ci
- Java: les types primitifs sont des types particuliers qui n'héritent pas du type Object, par exemple on ne peut pas faire:
- Les types Génériques: on été implémenté en même temps sur les deux langages, il permettent d'encapsuler des opérations non spécifique à un type de données particulier (Quand l'implémentation ne dépend pas des objets traitée, par exemple les collection: List, ...)
Exemple avec une List de String et une List d'Integer:
On crée une List de String auquel on ajoute deux chaines
Puis une List d'Integer auquel on ajoute deux entiers
Lorsque l'on essaye d'ajouter un entier à la List de String on obtient une erreur de compilation
- En Java: les types génériques devaient pouvoir s'éxécuter sur n'importe quel JVM non modifiée.
Pour cela chaque types génériques est remplacés par le type Object à la compilation. Ainsi au moment de l'éxecution la JVM ne traite que des cast:
- En C#: chaque type génériques possède un code particulier.
- En Java: les types génériques devaient pouvoir s'éxécuter sur n'importe quel JVM non modifiée.
- Prennons par exemple le code suivant ou on compare deux types génériques (des List<Integer> avec des List<String>) et on cherche à connaitre l'égalité
- Vrai en Java: car c'est une simple comparaison de List<Object>
- Faux en C#: car c'est une comparaison de List<String> et de List<Integer>

Benchmarks
Tableau comparatifs des vitesses d'execution de plusieurs langages sur des environnement différents:

On remarque une légère avance du .NET sur le Java pour un environnement Windows et inversement sur un environnement Linux (le C++ est quand a lui toujours gagnant puisqu'il est compilé et non interprété)