ABCSITE

INFORMATIQUE

 
 
 

 
 

 Java 

La gestion de la mémoire

 

Java et la mémoire inaccessible

Comme nous l'avons déjà vu, durant l'exécution d'un programme Java, vous êtes emmené à créer de objet (la plupart du temps en utilisant l'opérateur new). Ces objets, crées dans une mémoires appelée tas (head), sont utilisés, puis au bout d'un certain temps tombent aux oubliettes.

En effet, en Java, un objet est obligatoirement référencé par un pointeur (même si l'on ne manipule pas directement cette entité). Ce pointeur est connut soit par l'intermédiaire d'un identificateur (une variable, un paramètre d'appel de méthode, ou un attribut de classe), soit il fait parti d'une collection (un tableau ou, pourquoi pas, un objet de la classe java.util.Vector, qui de toutes façon encapsule un tableau élémentaire).

Mais que ce passe t'il dés lors que l'identificateur utilisé pour référencer l'objet (ou celui du conteneur) devient hors de portée ? L'exemple suivant illustre le problème.

public void fctQuelconque() {
MyClass myObject = new MyClass();
myObject.appelMethode1();
myObject.appelMethode2();
}


Jusqu'à maintenant, nous avons dit que cette zone de mémoire (l'espace occupé par les attributs de l'objet inutilisé) était simplement désallouée. C'est vrai, mais le mécanisme est en fait un petit peu plus complexe que cela. Un système relativement complexe, le garbage collector, est chargé de repérer les objets qui ne sont plus référencés, est de les supprimer de la mémoire.

Un petit problème se pose malgré tout : on risque de morceler la mémoire de telle sorte qu'elle ressemble au final à un gros gruyère. Pour palier ce problème, le garbage collector est aussi chargé de défragmenter les zones de mémoire pour laisser de vastes plages de mémoire plutôt de d'infinis petit trous. Mais d'autres circonstances peuvent aussi amener à devoir libérer l'espace mémoire utilisé par un objet. Voici quelques exemples.

public void fctQuelconque() {
MyClass myObject = new MyClass();
myObject.appelMethode1();
myObject = new MyClass();
myObject.appelMethode2();
}


public void fctQuelconque() {
MyClass myObject = new MyClass();
myObject.appelMethode1();
myObject = null; // L'objet n'est plus utilisable !
// . . . 
}


D'autres exemples de code pourraient être mis en évidence, notamment au niveau de la manipulation des attributs de classes. Je vous laisse pensez à ces problèmes.

Le ramasse miettes

Le ramasse miettes (ou Garbage Collector en anglais) est un un système qui est chargé de libérer les espaces mémoire qui sont devenus inutilisables.

Il s'avère que le ramasse miettes ne fait pas exactement partie des spécifications du langage Java. Or toutes les implémentations de machines virtuelles actuelles en possède un. Cependant, leur fonctionnement peut différer d'une implémentation à une autre. Dans la suite de ce chapitre, nous allons étudier le fonctionnement du ramasse miettes de la machine virtuelle du JDK.

La JVM du JDK

Le problème principal du Garbage Collector est de déterminer quels sont les objets susceptible d'être supprimés de la mémoire.

Une première approche consisterait à dire qu'il suffit de compter le nombre de référence sur un objet. A chaque duplication d'un pointeur sur un objet, il suffirait d'incrémenter ce nombre. A chaque perte de référence, il faudrait alors le décrémenter. Si cette valeur tombe à zéro, l'objet peut être supprimé. Or cette approche présente un problème : si deux objets mettent en place des références circulaires, et si ces objets ne sont plus utilisables, ils ne seraient pas pour autant susceptible d'être supprimés de la mémoire.

Une approche plus sérieuse consiste à parcourir tous les objets référençable à partir des objets racines et de les marquer comme accessible. Une fois cette étapes réalisée, tous objets non accessible est donc supprimables, et ce quelques soit le nombre de références qui le pointe. Ce mécanisme est qualifié (en anglais) de "mark-sweep garbage collector" (marquer-balayer).

Mode de fonctionnement du ramasse miettes

Le ramasse miette du JDK peut de plus fonctionner selon deux modes : un mode synchrone et un mode asynchrone.

Le ramasse miettes fonctionnent en mode asynchrone dès lors que votre programme est en attente. Ainsi le ramasse miettes peut travailler sans réellement ralentir votre application, en profitant par exemple, du fait qu'il est en attente d'un évènement utilisateur sur son l'interface graphique. Dans ce mode, seul le marquage des objets supprimables est réalisé.

Mais le ramasse miettes peut aussi fonctionner selon un autre mode : le mode synchrone. Dans ce cas, il répond à une demande explicite de nettoyage de la mémoire. Cette demande peut intervenir dans deux cas particuliers. Le premier cas est déclenché dès lors que la machine virtuelle cherche à instancier un nouvel objet et que l'espace libre passe en dessous d'une certaine valeur (c'est la JVM qui invoque le GC). Le seconde cas correspond à une demande explicite, de la part du programme.

Pour invoquer explicitement le GC, il suffit d'exécuter l'ordre suivant : System.gc();

Il s'avère que le mode asynchrone de fonctionnement du GC peut être inhibé. Dans ce cas, lorsque le GC est invoqué (de manière synchrone) il doit aussi préalablement marquer les objets supprimables.

Pour désactiver le mode asynchrone du GC, il faut lancer la JVM (Java Virtual Machine) dans un mode particulier. Pour ce faire, utilisez l'option -noasyncgc sur la ligne de commande.

Les finaliseurs

Les finaliseurs sont des méthodes qui sont invoquée dès lors que le GC souhaite détruire l'objet. Ainsi, si votre objet à une dernière volonté, il pourra l'exaucer. En fait, il n'y a qu'un seul finaliseur par classe. Son prototype est des plus simple : il ne prend aucun paramètres et renvoie un résultat vide (void). Son nom est ... et oui, finalize.

Ces méthodes (parfois appelée destructeurs) sont fort utiles dans les langages de programmation orientés objets : elles permettent le plus souvent, lors de la destruction de l'objet, de libérer les ressources allouées durant la construction d'un objet. Par les terme de ressources, c'est de la mémoire qui est le plus souvent sous-entendu. Or, en Java, la mémoire est automatiquement désallouée. Ces méthodes sont elles donc utiles en Java ? La réponse est simple : oui, mais pas souvent.

Ces méthodes sont nécessaire en Java. Lors de la destruction de l'objet, ce dernier peut avoir à désallouer des ressources autres que de la mémoire. Sans elles, et dans certains cas, la programmation de vos programme serait bien plus lourde. Mais il est bien clair, que les finaliseurs (ou destructeurs) sont bien moins utiles qu'en C++, par exemple.

L'exemple suivant est fort intéressant : il permet de bien mettre en évidence le fonctionnement du ramasse miettes. En effet il créer un grand nombre d'objets, rendu de suite inutiles. En conséquence, le seul limite de mémoire disponible est fréquemment atteint, ce qui a pour but de lancer le "garbage collector".

public class Garbage {
String id;
public Garbage(int i) {
id = "" + i;
System.out.println("Nouvel objet " + id + " - ");
}
public void finalize() {
System.out.println("Destruction de objet " + id + " - ");
}
public static void main(String argv[]) {
for(int i=0;i<100000;i++) new Garbage(i);
}
}


Notez un détail important : l'affichage sur la console est un mécanisme coûteux en temps d'exécution.

Conclusion

J'espère que ce chapitre vous aura permis de rendre le fonctionnement du ramasse miettes un peu moins mystérieux. De même, notez bien que vous pouvez, dans une certaine mesure, interférer sur ce mécanisme, pour améliorer ces performances.

 
 

ABCSITE © copyright 2002