Développer en Java/La machine virtuelle Java (JVM)

Introduction modifier

La JVM est comme son nom l'indique une machine virtuelle, c'est-à-dire un logiciel qui reproduit le fonctionnement d'un processeur physique. Elle constitue donc une couche intermédiaire entre le programme java compilé et le système d'exploitation. Elle possède son propre jeu d'instruction et sa propre gestion de la mémoire. Le fonctionnement de la JVM et les API java sont spécifiés par des standards. De fait, plusieurs implémentations de JVM existent, certaines fonctionnant sur plusieurs systèmes d'exploitation.

Le byte code modifier

 
Le même byte-code fonctionne sur toutes les JVM

Le byte code est le code produit par la compilation du java, il est exprimé dans un langage assembleur. Tandis que les langages assembleur sont spécifiques à un type de processeur particulier, le byte-code java vise à être exécuté sur la machine virtuelle Java. C'est ce qui permet à Java d'être multi-plateforme : en effet, Java garantit qu'un même byte-code sera exécuté de la même façon par la JVM quel que soit le système d'exploitation ou le type de processeur.

Toutefois, en fonction de la version du compilateur java utilisé, le bytecode ne sera pas compatible avec toutes les versions de JVM : par exemple un code 1.5 ne fonctionnera pas avec une JVM 1.4. Il faut utiliser la même version ou plus récente de l'environnement d'exécution que celle du compilateur utilisé pour l'application.

Aperçu modifier

Le JDK possède un outil permettant de désassembler les classes compilées : javap.

À partir d'une classe compilée, il permet :

  • d'extraire la signature des membres (méthodes et attributs) d'une classe qu'ils soient publics, protégés, privés ou package.
  • de désassembler le code des méthodes de la classe (option -c).

Exemple : Extrait du code d'une méthode généré par javap

public void setEnv(java.lang.String, java.lang.String);
  Code:
   0:   aload_0
   1:   getfield        #43; //Field m_env:Ljava/util/Map;
   4:   aload_1
   5:   aload_2
   6:   invokeinterface #219,  3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
   11:  pop
   12:  return

Le code java fonctionne à l'aide d'une pile où les opérandes des expressions et les arguments des méthodes appelées sont empilés. Les opérateurs et méthodes appelées dépilent leur opérandes/arguments de cette pile et y empile leur résultat.

L'instruction aload visible dans cet extrait permet d'empiler la valeur d'un argument sur la pile (0 étant le pointeur this, 1 le premier argument, ...). Les numéros précédés d'un caractère dièse # font référence à une table de constantes dans la classe. Ces numéros sont traduits par javap qui ajoute un commentaire en fin de ligne.

Pour plus de détails, voir la spécification de la JVM (en anglais) sur le site d'Oracle : http://docs.oracle.com/javase/specs/jvms/se7/html/index.html

Il existe par ailleurs divers programmes pour décompiler des classes, c'est-à-dire retrouver le code source initial à partir des fichiers .class, comme le freeware portable JD-GUI téléchargeable sur http://jd.benow.ca/.

Cependant, les commentaires permettant de comprendre et de documenter le code sont perdus car absents du fichier compilé. De plus, certaines structures de code ne sont pas supportées par ce genre d'outil, ni les nouvelles fonctionnalités de Java tels que les types génériques (informations perdues à la compilation).

Compilation "juste à temps" modifier

La compilation "juste à temps" (just-in-time compilation ou JIT) traduit le byte-code en code machine pour le processeur de la machine où la JVM est lancée. Cela permet d'améliorer les performances des applications Java exécutées. Elle est appelée "juste à temps" car elle intervient juste avant le premier appel de chaque méthode.

 
Fonctionnement de la compilation « juste à temps » de Java

La gestion de la mémoire modifier

La JVM possède sa propre gestion de la mémoire qui est indépendante de celle du système d'exploitation. La mémoire allouée à la JVM est bornée (possède un minimum et un maximum), des mécanismes particuliers étant chargés de la gestion de cette quantité de mémoire limitée. Ces mécanismes sont appelés le ramasse-miette (garbage collector) : en effet le langage java ne permet pas de désallouer directement la mémoire une fois qu'un objet n'est plus utilisé. Le ramasse-miette est chargé de nettoyer les objets inutilisés à intervalle régulier et ainsi de toujours maintenir une quantité de mémoire suffisante pour permettre aux programmes java de fonctionner.

Algorithme de ramasse-miette modifier

L'algorithme utilisé par le ramasse-miette est un facteur très important pour améliorer ou dégrader les performances d'un programme. Lors de certaines phases de son exécution, toute l'activité de la JVM est gelée, ce qui pose des problèmes de temps de réponse. D'autre part, lorsque ce mécanisme s'enclenche trop souvent, il peut consommer énormément de cycles processeur.

Les implémentations de la JVM modifier

Dalvik modifier

Principalement utilisée sous Android.

Open JDK modifier

Machine virtuelle open-source.