 |
 |
4) Initialisation & nettoyage |
|
 |
|
Texte original |
 |
Traducteur : F. DEFAIX et Y. CHICHA |
|
 |
///
|
Ce chapitre contient 6 pages
1
2
3
4
5
6
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
After reading this, you probably get the
idea that you won’t use finalize( ) much. You’re
correct; it is not the appropriate place for normal cleanup to occur. So where
should normal cleanup be
performed?
|
 |
Maintenant, vous vous dites probablement que vous n'allez pas beaucoup
utiliser finalize( ). Vous avez raison : ce n'est pas l'endroit
approprié pour effectuer des opérations normales de nettoyage. Dans ce cas, où celles-ci
doivent-elles se passer ?
|
 |
 |
 |
You must perform cleanup
|
 |
Le nettoyage est impératif
|
 |
 |
 |
To clean up an object, the user of that
object must call a cleanup method at the point the
cleanup is desired. This sounds pretty straightforward, but it collides a bit
with the C++ concept of the destructor. In C++, all
objects are destroyed. Or rather, all objects should be destroyed. If the
C++ object is created as a local (i.e., on the stack—not possible in
Java), then the destruction happens at the closing curly brace of the scope in
which the object was created. If the object was created using new (like
in Java) the destructor is called when the programmer calls the C++ operator
delete (which doesn’t exist in Java). If the C++ programmer forgets
to call delete, the destructor is never called and you have a memory
leak, plus the other parts of the object never get cleaned up. This kind of bug
can be very difficult to track down.
|
 |
Pour nettoyer un objet, son utilisateur doit appeler une méthode de
nettoyage au moment où celui-ci est nécessaire. Cela semble assez simple, mais se heurte au concept
de destructeur de C++. En C++, tous les objets sont, ou plutôt devraient être, détruits.
Si l'objet C++ est créé localement (c'est à dire sur la pile, ce qui n'est pas possible en Java),
alors la destruction se produit à la fermeture de la portée dans laquelle l'objet a été créé. Si
l'objet a été créé par new (comme en Java) le destructeur est appelé quand le
programmeur appelle l'opérateur C++ delete (cet opérateur n'existe pas en Java).
Si le programmeur C++ oublie d'appeler delete, le destructeur n'est jamais appelé
et l'on obtient une fuite mémoire. De plus les membres de l'objet ne sont jamais nettoyé non plus.
Ce genre de bogue peut être très difficile à repérer.
|
 |
 |
 |
In contrast, Java doesn’t allow you
to create local objects—you must always use new. But in Java,
there’s no “delete” to call to release the object since the
garbage collector releases the storage for you. So from a simplistic standpoint
you could say that because of garbage collection, Java has no destructor.
You’ll see as this book progresses, however, that the presence of a
garbage collector does not remove the need for or utility
of destructors. (And you should never call
finalize( ) directly, so that’s not an
appropriate avenue for a solution.) If you want some kind of cleanup performed
other than storage release you must still explicitly call an appropriate
method in Java, which is the equivalent of a C++ destructor without the
convenience.
|
 |
Contrairement à C++, Java ne permet pas de créer des objets locaux,
new doit toujours être utilisé. Cependant Java n'a pas de «delete» pour libérer
l'objet car le ramasse-miettes se charge automatiquement de récupérer la mémoire. Donc d'un point
de vue simplistique, on pourrait dire qu'à cause du ramasse-miettes, Java n'a pas de destructeur.
Cependant à mesure que la lecture de ce livre progresse, on s'aperçoit que la présence d'un
ramasse-miettes ne change ni le besoin ni l'utilité des destructeurs (de plus,
finalize( ) ne devrait jamais être appelé directement, ce n'est donc pas une
bonne solution pour ce problème). Si l'on a besoin d'effectuer des opérations de nettoyage autre
que libérer la mémoire, il est toujours nécessaire d'appeler explicitement la méthode
correspondante en Java, ce qui correspondra à un destructeur C++ sans être aussi
pratique.
|
 |
 |
 |
One of the things finalize( )
can be useful for is observing the process of garbage collection. The following
example shows you what’s going on and summarizes the previous descriptions
of garbage collection:
|
 |
Une des utilisations possibles de finalize( ) est
l'observation du ramasse-miettes. L'exemple suivant montre ce qui se passe et résume les
descriptions précédentes du ramasse-miettes :
|
 |
 |
 |
//: c04:Garbage.java // Demonstration of the garbage // collector and finalization
class Chair { static boolean gcrun = false; static boolean f = false; static int created = 0; static int finalized = 0; int i; Chair() { i = ++created; if(created == 47) System.out.println("Created 47"); } public void finalize() { if(!gcrun) { // The first time finalize() is called: gcrun = true; System.out.println( "Beginning to finalize after " + created + " Chairs have been created"); } if(i == 47) { System.out.println( "Finalizing Chair #47, " + "Setting flag to stop Chair creation"); f = true; } finalized++; if(finalized >= created) System.out.println( "All " + finalized + " finalized"); } }
public class Garbage { public static void main(String[] args) { // As long as the flag hasn't been set, // make Chairs and Strings: while(!Chair.f) { new Chair(); new String("To take up space"); } System.out.println( "After all Chairs have been created:\n" + "total created = " + Chair.created + ", total finalized = " + Chair.finalized); // Optional arguments force garbage // collection & finalization: if(args.length > 0) { if(args[0].equals("gc") || args[0].equals("all")) { System.out.println("gc():"); System.gc(); } if(args[0].equals("finalize") || args[0].equals("all")) { System.out.println("runFinalization():"); System.runFinalization(); } } System.out.println("bye!"); } } ///:~
|
 |
//: c04:Garbage.java // Démonstration du ramasse-miettes // et de la finalisation
class Chair { static boolean gcrun = false; static boolean f = false; static int created = 0; static int finalized = 0; int i; Chair() { i = ++created; if(created == 47) System.out.println("Created 47"); } public void finalize() { if(!gcrun) { // Premier appel de finalize() : gcrun = true; System.out.println( "Beginning to finalize after " + created + " Chairs have been created"); } if(i == 47) { System.out.println( "Finalizing Chair #47, " + "Setting flag to stop Chair creation"); f = true; } finalized++; if(finalized >= created) System.out.println( "All " + finalized + " finalized"); } }
public class Garbage { public static void main(String[] args) { // Tant que le flag n'a pas été levé, // construire des objets Chair et String: while(!Chair.f) { new Chair(); new String("To take up space"); } System.out.println( "After all Chairs have been created:\n" + "total created = " + Chair.created + ", total finalized = " + Chair.finalized); // Arguments optionnels pour forcer // la finalisation et l'exécution du ramasse-miettes : if(args.length > 0) { if(args[0].equals("gc") || args[0].equals("all")) { System.out.println("gc():"); System.gc(); } if(args[0].equals("finalize") || args[0].equals("all")) { System.out.println("runFinalization():"); System.runFinalization(); } } System.out.println("bye!"); } } ///:~
|
 |
 |
 |
The above program creates many
Chair objects, and at some point after the garbage collector begins
running, the program stops creating Chairs. Since the garbage collector
can run at any time, you don’t know exactly when it will start up, so
there’s a flag called gcrun to indicate whether the garbage
collector has started running yet. A second flag f is a way for
Chair to tell the main( ) loop that it should stop making
objects. Both of these flags are set within finalize( ), which is
called during garbage collection.
|
 |
Le programme ci-dessus crée un grand nombre d'objets Chair
et, à un certain point après que le ramasse-miettes ait commencé à s'exécuter, le programme arrête
de créer des Chairs. Comme le ramasse-miettes peut s'exécuter n'importe quand, on
ne sait pas exactement à quel moment il se lance, il existe donc un flag appelé
gcrun qui indique si le ramasse-miettes a commencé son exécution. Un deuxième
flag f est le moyen pour Chair de prévenir la boucle
main( ) qu'elle devrait arrêter de fabriquer des objets. On lève ces deux
flags dans finalize( ), qui est appelé pendant l'exécution du
ramasse-miettes.
|
 |
 |
 |
Two other static variables,
created and finalized, keep track of the number of Chairs
created versus the number that get finalized by the garbage collector. Finally,
each Chair has its own (non-static) int i so it can
keep track of what number it is. When Chair number 47 is finalized, the
flag is set to true to bring the process of Chair creation to a
stop.
|
 |
Deux autres variables statiques, created
and finalized, enregistre le nombre d'objets Chair créés par
rapport au nombre réclamé par le ramasse-miettes. Enfin, chaque objet Chair
contient sa propre version (non statique) de l'int i pour savoir
quel est son numéro. Quand l'objet Chair numéro 47 est réclamé, le flag est mis à
true pour arrêter la création des objets Chair.
|
 |
 |
 |
All this happens in main( ),
in the loop
|
 |
Tout ceci se passe dans le main( ), dans la
boucle
|
 |
 |
 |
while(!Chair.f) { new Chair(); new String("To take up space"); }
|
 |
while(!Chair.f) { new Chair(); new String("To take up space"); }
|
 |
 |
 |
You might wonder how this loop could ever
finish, since there’s nothing inside the loop that changes the value of
Chair.f. However, the finalize( ) process will, eventually,
when it finalizes number 47.
|
 |
On peut se demander comment cette boucle va se terminer puisque rien dans
la boucle ne change la valeur de Chair.f. Cependant,
finalize( ) le fera au moment de la réclamation du numéro 47.
|
 |
 |
 |
The creation of a String object
during each iteration is simply extra storage being allocated to encourage the
garbage collector to kick in, which it will do when it starts to get nervous
about the amount of memory available.
|
 |
La création d'un objet String à chaque itération
représente simplement de l'espace mémoire supplémentaire pour inciter le ramasse-miettes à
s'exécuter, ce qu'il fera dès qu'il se sentira inquiet pour le montant de mémoire
disponible.
|
 |
 |
 |
When you run the program, you provide a
command-line argument of “gc,” “finalize,” or
“all.” The “gc” argument will call the
System.gc( ) method (to force execution of
the garbage collector). Using the “finalize” argument calls
System.runFinalization( ) which—in
theory—will cause any unfinalized objects to be finalized. And
“all” causes both methods to be called.
|
 |
A l'exécution du programme, l'utilisateur fournit une option sur la ligne
de commande : «gc,» «finalize,» ou «all». Le paramètre «gc» permet l'appel de la méthode
System.gc( ) (pour forcer l'exécution du ramasse-miettes). «finalize» permet
d'appeler System.runFinalization( ) ce qui, en théorie, fait que tout objet
non finalisé soit finalisé. Enfin, «all» exécute les deux méthodes.
|
 |
 |
 |
The behavior of this program and the
version in the first edition of this book shows that the whole issue of garbage
collection and finalization has been evolving, with much of the evolution
happening behind closed doors. In fact, by the time you read this, the behavior
of the program may have changed once again.
|
 |
Le comportement de ce programme et celui de la version de la première
édition de cet ouvrage montrent que la question du ramasse-miettes et de la finalisation a évolué
et qu'une grosse part de cette évolution s'est passée en coulisse. En fait, il est possible que le
comportement du programme soit tout à fait différent lorsque vous lirez ces lignes.
|
 |
 |
 |
If System.gc( ) is called,
then finalization happens to all the objects. This was not necessarily the case
with previous implementations of the JDK, although the documentation claimed
otherwise. In addition, you’ll see that it doesn’t seem to make any
difference whether System.runFinalization( ) is
called.
|
 |
Si System.gc( ) est appelé, alors la finalisation
concerne tous les objets. Ce n'était pas forcément le cas avec les implémentations précédentes du
JDK bien que la documentation dise le contraire. De plus, il semble qu'appeler
System.runFinalization( ) n'ait aucun effet.
|
 |
 |
 |
However, you will see that only if
System.gc( ) is called after all the objects are created and
discarded will all the finalizers be called. If you do not call
System.gc( ), then only some of the objects will be finalized. In
Java 1.1, a method System.runFinalizersOnExit( ) was introduced that
caused programs to run all the finalizers as they exited, but the design turned
out to be buggy and the method was deprecated. This is yet another clue that the
Java designers were thrashing about trying to solve the garbage collection and
finalization problem. We can only hope that things have been worked out in Java
2.
|
 |
Cependant, on voit que toutes les méthodes de finalisation sont exécutées
seulement dans le cas où System.gc( ) est appelé après que tous les objets
aient été créés et mis à l'écart. Si System.gc( ) n'est pas appelé, seulement
certains objets seront finalisés. En Java 1.1, la méthode
System.runFinalizersOnExit( ) fut introduite pour que les programmes puissent
exécuter toutes les méthodes de finalisation lorsqu'ils se terminent, mais la conception était
boguée et la méthode a été classée deprecated. C'est un indice supplémentaire qui montre
que les concepteurs de Java ont eu de nombreux démêlés avec le problème du ramasse-miettes et de la
finalisation. Il est à espérer que ces questions ont été réglées dans Java 2.
|
 |
 |
 |
The preceding program shows that the
promise that finalizers will always be run holds true, but only if you
explicitly force it to happen yourself. If you don’t cause
System.gc( ) to be called, you’ll get an output like
this:
|
 |
Le programme ci-dessus montre que les méthodes de finalisation sont
toujours exécutées mais seulement si le programmeur force lui-même l'appel. Si on ne force pas
l'appel de System.gc( ), le résultat ressemblera à ceci :
|
 |
 |
 |
Created 47 Beginning to finalize after 3486 Chairs have been created Finalizing Chair #47, Setting flag to stop Chair creation After all Chairs have been created: total created = 3881, total finalized = 2684 bye!
|
 |
Created 47 Beginning to finalize after 3486 Chairs have been created Finalizing Chair #47, Setting flag to stop Chair creation After all Chairs have been created: total created = 3881, total finalized = 2684 bye!
|
 |
 |
 |
Thus, not all finalizers get called by
the time the program completes. If System.gc( ) is called, it will
finalize and destroy all the objects that are no longer in use up to that point.
|
 |
Toutes les méthodes de finalisation ne sont donc pas appelées à la fin du
programme. Ce n'est que quand System.gc( ) est appelé que tous les objets qui
ne sont plus utilisés seront finalisés et détruits.
|
 |
 |
 |
Remember that neither garbage collection
nor finalization is guaranteed. If the Java Virtual Machine (JVM) isn’t
close to running out of memory, then it will (wisely) not waste time recovering
memory through garbage collection.
|
 |
Il est important de se souvenir que ni le ramasse-miettes, ni la
finalisation ne sont garantis. Si la machine virtuelle Java (JVM) ne risque pas de manquer de
mémoire, elle ne perdra (légitimement) pas de temps à en récupérer grâce au
ramasse-miettes.
|
 |
 |
 |
The death condition
|
 |
La «death condition»
|
 |
 |
 |
In general, you
can’t rely on finalize( ) being called, and you must create
separate “cleanup” functions and call them explicitly. So it appears
that finalize( ) is only useful for obscure memory cleanup that most
programmers will never use. However, there is a very interesting use of
finalize( ) which does not rely on it being called every time. This
is the verification of the death
condition[29]
of an object.
|
 |
En général, on ne peut pas compter sur un appel à
finalize( ), et il est nécessaire de créer des fonctions spéciales de
nettoyage et de les appeler explicitement. Il semblerait donc que finalize( )
ne soit utile que pour effectuer des tâches de nettoyage mémoire très spécifiques dont la plupart
des programmeurs n'aura jamais besoin. Cependant, il existe une très intéressante utilisation de
finalize( ) qui ne nécessite pas que son appel soit garanti. Il s'agit de la
vérification de la death condition [29] d'un objet (état
d'un objet à sa destruction).
|
 |
 |
 |
At the point that you’re no longer
interested in an object—when it’s ready to be cleaned up—that
object should be in a state whereby its memory can be safely released. For
example, if the object represents an open file, that file should be closed by
the programmer before the object is garbage-collected. If any portions of the
object are not properly cleaned up, then you have a bug in your program that
could be very difficult to find. The value of finalize( ) is that it
can be used to discover this condition, even if it isn’t always called. If
one of the finalizations happens to reveal the bug, then you discover the
problem, which is all you really care about.
|
 |
Au moment où un objet n'est plus intéressant, c'est à dire lorsqu'il est
prêt à être réclamé par le ramasse-miettes, cet objet doit être dans un état où sa mémoire peut
être libérée sans problème. Par exemple, si l'objet représente un fichier ouvert, celui-ci doit
être fermé par le programmeur avant que la mémoire prise par l'objet ne soit réclamée. Si certaines
parties de cet objet n'ont pas été nettoyées comme il se doit, il s'agit d'un bogue du programme
qui peut être très difficile à localiser. L'intérêt de finalize( ) est qu'il
est possible de l'utiliser pour découvrir cet état de l'objet, même si cette méthode n'est pas
toujours appelée. Si une des finalisations trouve le bogue, alors le problème est découvert et
c'est ce qui compte vraiment après tout.
|
 |
 |
 |
Here’s a simple example of how you
might use it:
|
 |
Voici un petit exemple pour montrer comment on peut l'utiliser :
|
 |
 |
 |
//: c04:DeathCondition.java // Using finalize() to detect an object that // hasn't been properly cleaned up.
class Book { boolean checkedOut = false; Book(boolean checkOut) { checkedOut = checkOut; } void checkIn() { checkedOut = false; } public void finalize() { if(checkedOut) System.out.println("Error: checked out"); } }
public class DeathCondition { public static void main(String[] args) { Book novel = new Book(true); // Proper cleanup: novel.checkIn(); // Drop the reference, forget to clean up: new Book(true); // Force garbage collection & finalization: System.gc(); } } ///:~
|
 |
//: c04:DeathCondition.java // Comment utiliser finalize() pour détecter les objets qui // n'ont pas été nettoyés correctement.
class Book { boolean checkedOut = false; Book(boolean checkOut) { checkedOut = checkOut; } void checkIn() { checkedOut = false; } public void finalize() { if(checkedOut) System.out.println("Error: checked out"); } }
public class DeathCondition { public static void main(String[] args) { Book novel = new Book(true); // Nettoyage correct : novel.checkIn(); // Perd la référence et oublie le nettoyage : new Book(true); // Force l'exécution du ramasse-miettes et de la finalisation : System.gc(); } } ///:~
|
 |
 |
 |
The death condition is that all
Book objects are supposed to be checked in before they are
garbage-collected, but in main( ) a programmer error doesn’t
check in one of the books. Without finalize( ) to verify the death
condition, this could be a difficult bug to find.
|
 |
Ici, la «death condition» est le fait que tous les objets de type
Book doivent être «rendus» (checked in) avant d'être récupéré par le
ramasse-miettes, mais dans la fonction main( ) une erreur de programmation
fait qu'un de ces livres n'est pas rendu. Sans finalize( ) pour vérifier la
«death condition», cela pourrait s'avérer un bogue difficile à trouver.
|
 |
 |
 |
Note that System.gc( ) is
used to force finalization (and you should do this during program development to
speed debugging). But even if it isn’t, it’s highly probable that
the errant Book will eventually be discovered through repeated executions
of the program (assuming the program allocates enough storage to cause the
garbage collector to execute).
|
 |
Il est important de noter l'utilisation de
System.gc( ) pour forcer l'exécution de la finalisation (en fait, il est
utile de le faire pendant le développement du programme pour accélérer le débogage). Cependant même
si System.gc() n'est pas appelé, il est très probable que le livre
(Book) perdu soit découvert par plusieurs exécutions successives du programme (en
supposant que suffisamment de mémoire soit alloué pour que le ramasse-miettes se
déclenche).
|
 |
 |
 |
How a garbage collector works
|
 |
Comment fonctionne un ramasse-miettes ?
|
 |
 |
 |
If you come from a programming language
where allocating objects on the heap is expensive, you may naturally assume that
Java’s scheme of allocating everything (except primitives) on the heap is
expensive. However, it turns out that the garbage collector can have a
significant impact on increasing the speed of object creation. This might
sound a bit odd at first—that storage release affects storage
allocation—but it’s the way some JVMs work and it means that
allocating storage for heap objects in Java can be nearly as fast as creating
storage on the stack in other languages.
|
 |
Les utilisateurs de langages où l'allocation d'objets sur le tas coûte cher
peuvent supposer que la façon qu'a Java de tout allouer sur le tas (à l'exception des types de
base) coûte également cher. Cependant, il se trouve que l'utilisation d'un ramasse-miettes peut
accélérer de manière importante la création d'objets. Ceci peut sembler un peu bizarre à
première vue : la réclamation d'objets aurait un effet sur la création d'objets. Mais c'est
comme ça que certaines JVMs fonctionnent et cela veut dire, qu'en Java, l'allocation d'objets sur
le tas peut être presque aussi rapide que l'allocation sur la pile dans d'autres
langages.
|
 |
 |
 |
For example, you can think of the C++
heap as a yard where each object stakes out its own piece of turf. This real
estate can become abandoned sometime later and must be reused. In some JVMs, the
Java heap is quite different; it’s more like a conveyor belt that moves
forward every time you allocate a new object. This means that object storage
allocation is remarkably rapid. The “heap pointer” is simply moved
forward into virgin territory, so it’s effectively the same as C++’s
stack allocation. (Of course, there’s a little extra overhead for
bookkeeping but it’s nothing like searching for storage.)
|
 |
Un exemple serait de considérer le tas en C++ comme une pelouse où chaque
objet prend et délimite son morceau de gazon. Cet espace peut être abandonné un peu plus tard et
doit être réutilisé. Avec certaines JVMs, le tas de Java est assez différent ; il ressemble
plus à une chaîne de montage qui avancerait à chaque fois qu'un objet est alloué. Ce qui fait que
l'allocation est remarquablement rapide. Le «pointeur du tas» progresse simplement dans l'espace
vide, ce qui correspond donc à l'allocation sur la pile en C++ (il y a bien sûr une petite pénalité
supplémentaire pour le fonctionnement interne mais ce n'est pas comparable à la recherche de
mémoire libre).
|
 |
 |
 |
Now you might observe that the heap
isn’t in fact a conveyor belt, and if you treat it that way you’ll
eventually start paging memory a lot (which is a big performance hit) and later
run out. The trick is that the garbage collector steps in and while it collects
the garbage it compacts all the objects in the heap so that you’ve
effectively moved the “heap pointer” closer to the beginning of the
conveyor belt and further away from a page fault. The garbage collector
rearranges things and makes it possible for the high-speed, infinite-free-heap
model to be used while allocating storage.
|
 |
On peut remarquer que le tas n'est en fait pas vraiment une chaîne de
montage, et s'il est traité de cette manière, la mémoire finira par avoir un taux de «paging»
(utiliser toute la mémoire virtuelle incluant la partie sur disque dur) important (ce qui
représente un gros problème de performance) et finira par manquer de mémoire. Le ramasse-miettes
apporte la solution en s'interposant et, alors qu'il collecte les miettes (les objets
inutilisables), il compacte tous les objets du tas. Ceci représente l'action de déplacer le
«pointeur du tas» un peu plus vers le début et donc plus loin du «page fault» (interruption pour
demander au système d'exploitation des pages de mémoire supplémentaire situées dans la partie de la
mémoire virtuelle qui se trouve sur disque dur). Le ramasse-miettes réarrange tout pour permettre
l'utilisation de ce modèle d'allocation très rapide et utilisant une sorte de «tas
infini».
|
 |
 |
 |
To understand how this works, you need to
get a little better idea of the way the different garbage collector (GC) schemes
work. A simple but slow GC technique is reference counting. This means that each
object contains a reference counter, and every time a reference is attached to
an object the reference count is increased. Every time a reference goes out of
scope or is set to null, the reference count is decreased. Thus, managing
reference counts is a small but constant overhead that happens throughout the
lifetime of your program. The garbage collector moves through the entire list of
objects and when it finds one with a reference count of zero it releases that
storage. The one drawback is that if objects circularly refer to each other they
can have nonzero reference counts while still being garbage. Locating such
self-referential groups requires significant extra work for the garbage
collector. Reference counting is commonly used to explain one kind of garbage
collection but it doesn’t seem to be used in any JVM
implementations.
|
 |
Pour comprendre comment tout cela fonctionne, il serait bon de donner
maintenant une meilleure description de la façon dont un ramasse-miettes fonctionne. Nous
utiliserons l'acronyme GC (en anglais, un ramasse-miette est appelé Garbage Collector) dans les
paragraphes suivants. Une technique de GC relativement simple mais lente est le compteur de
référence. L'idée est que chaque objet contient un compteur de référence et à chaque fois qu'une
nouvelle référence sur un objet est créée le compteur est incrémenté. A chaque fois qu'une
référence est hors de portée ou que la valeur null lui est assignée, le compteur
de références est décrémenté. Par conséquent, la gestion des compteurs de références représente un
coût faible mais constant tout au long du programme. Le ramasse-miettes se déplace à travers toute
la liste d'objets et quand il en trouve un avec un compteur à zéro, il libère la mémoire.
L'inconvénient principal est que si des objets se référencent de façon circulaire, ils ne peuvent
jamais avoir un compteur à zéro tout en étant inaccessible. Pour localiser ces objets qui se
référencent mutuellement, le ramasse-miettes doit faire un important travail supplémentaire. Les
compteurs de références sont généralement utilisés pour expliquer les ramasses-miettes mais ils ne
semblent pas être utilisés dans les implémentations de la JVM.
|
 |
 |
 |
In faster schemes, garbage collection is
not based on reference counting. Instead, it is based on the idea that any
nondead object must ultimately be traceable back to a reference that lives
either on the stack or in static storage. The chain might go through several
layers of objects. Thus, if you start in the stack and the static storage area
and walk through all the references you’ll find all the live objects. For
each reference that you find, you must trace into the object that it points to
and then follow all the references in that object, tracing into the
objects they point to, etc., until you’ve moved through the entire web
that originated with the reference on the stack or in static storage. Each
object that you move through must still be alive. Note that there is no problem
with detached self-referential groups—these are simply not found, and are
therefore automatically garbage.
|
 |
D'autres techniques, plus performantes, n'utilisent pas de compteur de
références. Elles sont plutôt basées sur l'idée que l'on est capable de remonter la chaîne de
références de tout objet «non-mort» (i.e encore en utilisation) jusqu'à une référence vivant sur la
pile ou dans la zone statique. Cette chaîne peut très bien passer par plusieurs niveaux d'objets.
Par conséquent, si l'on part de la pile et de la zone statique et que l'on trace toutes les
références, on trouvera tous les objets encore en utilisation. Pour chaque référence que l'on
trouve, il faut aller jusqu'à l'objet référencé et ensuite suivre toutes les références contenues
dans cet objet, aller jusqu'aux objets référencés, etc. jusqu'à ce que l'on ait visité
tous les objets que l'on peut atteindre depuis la référence sur la pile ou dans la zone statique.
Chaque objet visité doit être encore vivant. Notez qu'il n'y a aucun problème avec les groupes qui
s'auto-référencent : ils ne sont tout simplement pas trouvés et sont donc automatiquement
morts.
|
 |
 |
 |
In the approach described here, the JVM
uses an adaptive garbage-collection scheme, and what it does with the
live objects that it locates depends on the variant currently being used. One of
these variants is stop-and-copy. This means that—for reasons that
will become apparent—the program is first stopped (this is not a
background collection scheme). Then, each live object that is found is copied
from one heap to another, leaving behind all the garbage. In addition, as the
objects are copied into the new heap they are packed end-to-end, thus compacting
the new heap (and allowing new storage to simply be reeled off the end as
previously described).
|
 |
Avec cette approche, la JVM utilise un ramasse-miettes adaptatif.
Le sort des objets vivants trouvés dépend de la variante du ramasse-miettes utilisée à ce
moment-là. Une de ces variantes est le stop-and-copy. L'idée est d'arrêter le programme
dans un premier temps (ce n'est pas un ramasse-miettes qui s'exécute en arrière-plan). Puis, chaque
objet vivant que l'on trouve est copié d'un tas à un autre, délaissant les objets morts. De plus,
au moment où les objets sont copiés, ils sont rassemblés les uns à côté des autres, compactant de
ce fait le nouveau tas (et permettant d'allouer de la mémoire en la récupérant à l'extrémité du tas
comme cela a été expliqué auparavant).
|
 |
 |
 |
Of course, when an object is moved from
one place to another, all references that point at (i.e., that reference)
the object must be changed. The reference that goes from the heap or the static
storage area to the object can be changed right away, but there can be other
references pointing to this object that will be encountered later during the
“walk.” These are fixed up as they are found (you could imagine a
table that maps old addresses to new ones).
|
 |
Bien entendu, quand un objet est déplacé d'un endroit à un autre, toutes
les références qui pointent (i.e. qui référencent) l'objet doivent être mis à jour. La
référence qui part du tas ou de la zone statique vers l'objet peut être modifiée sur le champ, mais
il y a d'autres références pointant sur cet objet qui seront trouvées «sur le chemin». Elles seront
corrigées dès qu'elles seront trouvées (on peut s'imaginer une table associant les anciennes
adresses aux nouvelles).
|
 |
 |
 |
There are two issues that make these
so-called “copy collectors” inefficient. The first is the idea that
you have two heaps and you slosh all the memory back and forth between these two
separate heaps, maintaining twice as much memory as you actually need. Some JVMs
deal with this by allocating the heap in chunks as needed and simply copying
from one chunk to another.
|
 |
Il existe deux problèmes qui rendent ces «ramasse-miettes par copie»
inefficaces. Le premier est l'utilisation de deux tas et le déplacement des objets d'un tas à
l'autre, utilisant ainsi deux fois plus de mémoire que nécessaire. Certaines JVMs s'en sortent en
allouant la mémoire par morceau et en copiant simplement les objets d'un morceau à un
autre.
|
 |
 |
 |
The second issue is the copying. Once
your program becomes stable it might be generating little or no garbage. Despite
that, a copy collector will still copy all the memory from one place to another,
which is wasteful. To prevent this, some JVMs detect that no new garbage is
being generated and switch to a different scheme (this is the
“adaptive” part). This other scheme is called mark and sweep,
and it’s what earlier versions of Sun’s JVM used all the time. For
general use, mark and sweep is fairly slow, but when you know you’re
generating little or no garbage it’s fast.
|
 |
Le deuxième problème est la copie. Une fois que le programme atteint un
état stable, il se peut qu'il ne génère pratiquement plus de miettes (i.e. d'objets morts). Malgré
ça, le ramasse-miettes par copie va quand même copier toute la mémoire d'une zone à une autre, ce
qui est du gaspillage put et simple. Pour éviter cela, certaines JVMs détectent que peu d'objets
meurent et choisissent alors une autre technique (c'est la partie d'«adaptation»). Cette autre
technique est appelée mark and sweep (NDT : litéralement marque et balaye),
et c'est ce que les versions précédentes de la JVM de Sun utilisaient en permanence. En général, le
«mark and sweep» est assez lent, mais quand on sait que l'on génère peu ou pas de miettes, la
technique est rapide.
|
 |
 |
 |
Mark and sweep follows the same logic of
starting from the stack and static storage and tracing through all the
references to find live objects. However, each time it finds a live object that
object is marked by setting a flag in it, but the object isn’t collected
yet. Only when the marking process is finished does the sweep occur. During the
sweep, the dead objects are released. However, no copying happens, so if the
collector chooses to compact a fragmented heap it does so by shuffling objects
around.
|
 |
La technique de «mark and sweep» suit la même logique de partir de la pile
et de la zone de mémoire statique et de suivre toutes les références pour trouver les objets encore
en utilisation. Cependant, à chaque fois qu'un objet vivant est trouvé, il est marqué avec un flag,
mais rien n'est encore collecté. C'est seulement lorsque la phase de «mark» est terminée que le
«sweep» commence. Pendant ce balayage, les objets morts sont libérés. Aucune copie n'est effectuée,
donc si le ramasse-miettes décide de compacter la mémoire, il le fait en réarrangeant les
objets.
|
 |
 |
 |
The “stop-and-copy” refers to
the idea that this type of garbage collection is not done in the
background; instead, the program is stopped while the GC occurs. In the Sun
literature you’ll find many references to garbage collection as a
low-priority background process, but it turns out that the GC was not
implemented that way, at least in earlier versions of the Sun JVM. Instead, the
Sun garbage collector ran when memory got low. In addition, mark-and-sweep
requires that the program be stopped.
|
 |
Le «stop-and-copy» correspond à l'idée que ce type de ramasse-miettes ne
s'exécute pas en tâche de fond, le programme est en fait arrêté pendant l'exécution du
ramasse-miettes. La littérature de Sun mentionne assez souvent le ramasse-miettes comme une tâche
de fond de basse priorité, mais il se trouve que le ramasse-miettes n'a pas été implémenté de cette
manière, tout au moins dans les premières versions de la JVM de Sun. Le ramasse-miettes était
plutôt exécuté quand il restait peu de mémoire libre. De plus, le «mark-and-sweep» nécessite
l'arrêt du programme.
|
 |
 |
 |
As previously mentioned, in the JVM
described here memory is allocated in big blocks. If you allocate a large
object, it gets its own block. Strict stop-and-copy requires copying every live
object from the source heap to a new heap before you could free the old one,
which translates to lots of memory. With blocks, the GC can typically use dead
blocks to copy objects to as it collects. Each block has a generation
count to keep track of whether it’s alive. In the normal case, only
the blocks created since the last GC are compacted; all other blocks get their
generation count bumped if they have been referenced from somewhere. This
handles the normal case of lots of short-lived temporary objects. Periodically,
a full sweep is made—large objects are still not copied (just get their
generation count bumped) and blocks containing small objects are copied and
compacted. The JVM monitors the efficiency of GC and if it becomes a waste of
time because all objects are long-lived then it switches to mark-and-sweep.
Similarly, the JVM keeps track of how successful mark-and-sweep is, and if the
heap starts to become fragmented it switches back to stop-and-copy. This is
where the “adaptive” part comes in, so you end up with a mouthful:
“adaptive generational stop-and-copy
mark-and-sweep.”
|
 |
Comme il a été dit précédemment, la JVM décrite ici alloue la mémoire par
blocs. Si un gros objet est alloué, un bloc complet lui est réservé. Le «stop-and-copy» strictement
appliqué nécessite la copie de chaque objet vivant du tas d'origine vers un nouveau tas avant de
pouvoir libérer le vieux tas, ce qui se traduit par la manipulation de beaucoup de mémoire. Avec
des blocs, le ramasse-miettes peut simplement utiliser les blocs vides (et/ou contenant uniquement
des objets morts) pour y copier les objets. Chaque bloc possède un compteur de génération
pour savoir s'il est « mort » (vide) ou non. Dans le cas normal, seuls les blocs créés depuis le
ramasse-miettes sont compactés ; les compteurs de générations de tous les autres blocs sont
mis à jour s'ils ont été référencés. Cela prend en compte le cas courant des nombreux objets ayant
une durée de vie très courte. Régulièrement, un balayage complet est effectué, les gros objets ne
sont toujours pas copiés (leurs compteurs de génération sont simplement mis à jour) et les blocs
contenant des petits objets sont copiés et compactés. La JVM évalue constamment l'efficacité du
ramasse-miettes et si cette technique devient une pénalité plutôt qu'un avantage, elle la change
pour un « mark-and-sweep ». De même, la JVM évalue l'efficacité du mark-and-sweep et si le tas se
fragmente, le stop-and-copy est réutilisé. C'est là où l'« adaptation » vient en place et finalement
on peut utiliser ce terme anglophone à rallonge : « adaptive generational stop-and-copy
mark-and-sweep » qui correspondrait à « adaptatif entre marque-et-balaye et stoppe-et-copie de façon
générationnelle ».
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |