7
Nous allons consacrer une série d’articles sur l’environnement d’exécution de java qui est la machine virtuelle de java. En tant que développeur java, il est parfois nécessaire de savoir ce qui se passe dans cette machine abstraite qu’est la JVM (Java Virtuel Machine) lorsque nous écrivons notre code java. Ceci nous facilite la maintenance, la résolution des différents problèmes, la compréhension des exceptions qui arrivent lors de l’exécution de nos programmes java. Outre cet aspect, il y a aussi l’écriture du bon code. Nos articles vont porter sur : L’architecture de la JVM. Compréhension de la mémoire en Java. La Structure d’une classe compilée Des différentes étapes dans la JVM de l’écriture du code source à l’exécution. Le Ramasse Miette (Garbage Collector) Calibrage de la JVM selon les contraintes et les besoins. L’architecture de la JVM La JVM est une machine abstraite, dans laquelle s’exécute du bytecode, c’est du code intermédiaire entre le langage machine binaire et le code source, il est généré après compilation du code source par le compilateur JAVAC. On distingue différentes implémentations de JVM, notamment Hotspot, JRockit, IBM, celle utilisée dans les environnements Android, Dalvik et plusieurs autres. Il faut distinguer trois aspects quand on parle de la JVM, il y a d’un côté la spécification et l’implémentation et de l’autre l’instance d’exécution. Dans notre développement nous pourrons faire mention des trois aspects. Nos articles seront consacrés à HotSpot qui est une implémentation Oracle.

Architecture de la jvm(1ere Partie) - JVM Architecture (First Part)

Embed Size (px)

Citation preview

Page 1: Architecture de la jvm(1ere Partie) - JVM Architecture (First Part)

Nous allons consacrer une série d’articles sur l’environnement d’exécution de java qui est la machine virtuelle de java.

En tant que développeur java, il est parfois nécessaire de savoir ce qui se passe dans cette machine abstraite qu’est la JVM (Java Virtuel Machine) lorsque nous écrivons notre code java. Ceci nous facilite la maintenance, la résolution des différents problèmes, la compréhension des exceptions qui arrivent lors de l’exécution de nos programmes java. Outre cet aspect, il y a aussi l’écriture du bon code.

Nos articles vont porter sur :

L’architecture de la JVM. Compréhension de la mémoire en Java. La Structure d’une classe compilée Des différentes étapes dans la JVM de l’écriture du code source à l’exécution. Le Ramasse Miette (Garbage Collector) Calibrage de la JVM selon les contraintes et les besoins.

L’architecture de la JVM

La JVM est une machine abstraite, dans laquelle s’exécute du bytecode, c’est du code intermédiaire entre le langage machine binaire et le code source, il est généré après compilation du code source par le compilateur JAVAC. On distingue différentes implémentations de JVM, notamment Hotspot, JRockit, IBM, celle utilisée dans les environnements Android, Dalvik et plusieurs autres. Il faut distinguer trois aspects quand on parle de la JVM, il y a d’un côté la spécification et l’implémentation et de l’autre l’instance d’exécution. Dans notre développement nous pourrons faire mention des trois aspects. Nos articles seront consacrés à HotSpot qui est une implémentation Oracle.

I-Responsabilité de la JVM.

Dans le processus d’exécution des programmes java, la JVM a pour responsabilité de garantir, un environnement sécure et une certaine abstraction au niveau de la couche système. Le leitmotiv des programmes Java quand ce langage fut créé en 1994, c’était Write Once Run EveryWhere qui veut dire après l’écriture de votre code source java, vous pouvez l’exécuter dans n’importe quel environnement. Pour ceux qui ont programmé en C ou en C++ savent que la

Page 2: Architecture de la jvm(1ere Partie) - JVM Architecture (First Part)

compilation et l’exécution des programmes écrits dans ces langages sont dépendants des environnements d’exécution.

Dans la responsabilité de la JVM, celle-ci garantie le chargement du code compilé (bytecode) , la vérification de l’intégrité du code chargé, l’assurance que celle-ci respecte bien la sémantique du langage java, l’allocation de mémoire, l’initialisation des variables, la résolution des liens symboliques et l’exécution du code chargé.

2-Comment la JVM garantie l’exécution de ces différents contrats?

2-1-Chargement des Classes

La JVM contient des chargeurs de classe qui lui permettent de charger celle-ci pour des fins d’exécution (Figure 1). Ils (chargeurs) s’occupent de mettre à la disposition de la JVM, les classes de l’API Java et vos classes nécessaires à l’exécution de votre programme.

Page 3: Architecture de la jvm(1ere Partie) - JVM Architecture (First Part)

Figure 1

On distingue 2 types de chargeurs de classe:

Le chargeur primordial encore appelé Bootstrap, qui est écrit en code natif (langage c) et parent de tous les chargeurs. Son rôle est le chargement du noyau de java(JDK) localisé dans ce jar : rt.jar

Les chargeurs définis par l’utilisateur (User-defined ClassLoader) écrits en java par l’utilisateur et compilés en bytecode.

La JVM fournit 3 chargeurs de classe par défaut : Bootstrap, Extension et System appelé encore chargeur de classe d’application.

Page 4: Architecture de la jvm(1ere Partie) - JVM Architecture (First Part)

Le fonctionnement des chargeurs de classe sont basés sur 3 principes fondamentaux :

La délégation La visibilité L’unicité

Outre ces principes, les chargeurs fonctionnent de manière hiérarchique (voir Figure 2) et respectent une architecture basée sur l’espace de nom (namespace). Nous y reviendrons dans cet article.

Délégation

Dans le principe de délégation, le chargeur de classe délègue le chargement d’une classe à son parent et se substitue à celui-ci s’il ne parvient pas à trouver ou à charger la dite classe.

Visibilité

Dans le principe de visibilité, le chargeur fils à la possibilité de voir les activités de chargement de son parent, en revanche, l’inverse n’est pas possible.

Unicité

Dans le principe d’unicité, toutes les classes sont chargées une seule fois selon l’espace de nom du chargeur. La délégation permet à chaque chargeur de vérifier le principe d’unicité, et grâce à la visibilité, le fils peut voir les activités de chargement de son parent.

Page 5: Architecture de la jvm(1ere Partie) - JVM Architecture (First Part)

Figure 2

Class.forname(), ClassLoader.loadClass(), l’API de réflexion, et JNI_FindClass sont les éléments de l’API de Java qui peuvent initier le chargement d’une classe ou d’une interface. Le chargement d’une classe nécessite au préalable le chargement de toutes les super classes (classes héritées) et toutes les superinterfaces (interfaces héritées). Il est à noter ici quand on parle de chargement, il est question aussi d’interfaces implémentées par les classes.

Pendant la phase de chargement d’une classe ou d’une interface, la machine virtuelle a la responsabilité de :

Page 6: Architecture de la jvm(1ere Partie) - JVM Architecture (First Part)

Produire une Stream du code binaire de la classe ou l’interface à charger. Parser la Stream en question, puis extraire les métas données de la classe ou de

l’interface en cours de chargement et l’enregistrer dans les structures appropriées dans la zone de mémoire prévue à cet effet (Method Area).

Créer une instance de java.lang.Class dans le Heap qui (java.lang.Class) représente le type de toutes les instances de cette classe.

Des erreurs peuvent modifier le flux d’exécution pendant la phase de chargement, notamment :

NoClassDefFoundError qui intervient lorsque la représentation binaire de la classe ou de l’interface à charger ne peut être trouvée

ClassFormatError qui intervient quand le format de la classe ou l’interface chargée ne respecte pas les spécifications

UnsuportedClassVersionError intervient également pendant la vérification du format de la syntaxe de la classe, si celui-ci ne respecte pas les spécifications.

ClasscircularityError intervient si la classe a un problème avec sa hiérarchie, au niveau de l’héritage, notamment si la classe est elle-même sa superclasse ou l’interface de sa superinterface.

IncompatibleClassChangeError intervient si le parent de l’interface n’est pas une interface ou alors le parent d’une classe est une interface.

Comme mentionné précédemment, les classes ou les interfaces sont chargées dans le nom d’espace de son chargeur, le nom d’espace est l’équivalent de package du point de vue de chargement dans la JVM. Ce principe implique la possibilité de voir une classe chargée plus d’une fois par des chargeurs différents.

L’ordinateur ne s’exprimant qu’en code binaire, il a besoin pour l’exécution du code chargé, une traduction en code machine ou code binaire. L’élément ayant cette responsabilité est le CPU ou le processeur. Cependant, il ne s’exprime qu’en assembleur et vu la versatilité de ce langage, dû au fait des constructeurs de processeur (INTEL, AMD etc…), on distingue deux types de langages, notamment, les langages compilés et les langages interprétés. Le C++, le C ou le Fortran sont considérés comme des langages compilés, parce que le code source est traduit en langage assembleur par un compilateur statique spécifique à un processeur, puis en langage machine. Contrairement à certains langages comme PERL ou PHP qui sont interprétés. Le code écrit en langage interprété peut s’exécuter sur n’importe quelle type de machine, tout aussi longtemps qu’elle possède le bon interpréteur prévu à cet effet. Ce qui se traduit par une perte de vitesse dans l’exécution du code interprété, parce que dans du code interprété, chaque instruction doit être traduite par l’interpréteur, et en plus il ne peut faire qu’une instruction à la fois. Ce qui n’est pas le cas dans le cas du code compilé. En somme les programmes écrits en langage interprétés sont portables mais lents et les programmes écrits en langage compilé, ne sont pas portables mais rapides. La JVM tire donc profit de ces deux notions grâce à son interpréteur pour la portabilité son compilateur la JIT (Just In Time) pour la vitesse d’exécution.

Page 7: Architecture de la jvm(1ere Partie) - JVM Architecture (First Part)