Nettoyage de Cycles
Traditionnellement, les mécanismes de comptage de références, comme utilisés auparavant
dans PHP, ne savent pas gérer les fuites mémoires dûes à des références circulaires ;
cependant depuis PHP 5.3.0, un algorithme synchrone issu de l'analyse
» Concurrent Cycle Collection in Reference Counted Systems
est utilisé pour répondre à ce problème particulier.
Une explication complète du fonctionnement de l'algorithme irait un peu au-delà du cadre de cette section,
mais nous allons ici présenter les principes de base. Avant tout, nous allons établir quelques règles de base.
Si un refcount est incrémenté, le conteneur est toujours utilisé, donc pas nettoyé. Si le refcount
est décrémenté et atteint zéro, le conteneur zval peut être supprimé et la mémoire libérée. Premièrement, ceci signifie
que les cycles perturbateur ne peuvent être créés que lorsque le refcount est décrémenté vers une valeur
différente de zéro. Ensuite, dans un cycle problématique, il est possible de détecter les déchets en
vérifiant s'il est possible ou non de décrémenter leur refcount de un, en vérifiant ensuite quelles zvals
ont un refcount à zéro.
Pour éviter d'avoir à appeler la routine de nettoyage à chaque décrémentation de refcount possible,
l'algorithme place toutes les zval racines dans un "tampon de racines" (en les marquant en "violet").
Il s'assure aussi que chaque racine n'apparaisse qu'une seule fois dans le tampon. Le
mécanisme de nettoyage n'intervient alors que lorsque le tampon est plein. Voyez l'étape A
sur la figure ci-dessus.
A l'étape B, l'algorithme lance une recherche sur toutes les racines possibles, afin de
décrémenter de une unité les refcounts de toutes les zvals qu'il trouve, en faisant bien
attention de ne pas décrémenter deux fois le refcount de la même zval (en les marquant
comme "grises"). A l'étape C, l'algorithme relance une recherche sur toutes les racines
possibles et scrute la valeur de refcount de chaque zval. S'il trouve un refcount à zéro,
la zval est marquée comme "blanche" (bleu sur la figure). S'il trouve une valeur supérieure à zéro,
il annule la décrémentation du refcount en refaisant une recherche à partir de ce nœud, et les
marque comme "noires" à nouveau. Dans la dernière étape, D, l'algorithme parcourt tout le
tampon des racines et les supprime, tout en scrutant chaque zval ; toute zval marquée
comme "blanche" à l'étape précédente sera alors supprimée de la mémoire.
Maintenant que vous savez globalement comment l'algorithme fonctionne, nous allons
voir comment il a été intégré dans PHP. Par défaut, le ramasse-miettes de PHP est
activé. Il existe cependant une options de php.ini pour changer cela :
zend.enable_gc.
Lorsque le ramasse-miettes est activé, l'algorithme de recherche des cycles
décrit ci-dessus est exécuté à chaque fois que le tampon est plein. Le tampon de
racines a une taille fixée à 10.000 racines (ce paramètre est changeable grâce à
GC_THRESHOLD_DEFAULT
dans Zend/zend_gc.c
dans le code source de PHP, une recompilation est donc nécessaire). Si le ramasse-
miettes est désactivé, la recherche des cycles l'est aussi. Cependant, les racines
possibles seront toujours enregistrées dans le tampon, ceci ne dépend pas de l'activation
du ramasse-miettes.
Si le tampon est plein alors que le mécanisme de nettoyage est désactivé, les racines
ne seront plus enregistrées. Ces racines ne seront donc jamais analysées par l'algorithme,
et si elles faisaient partie de références circulaires, elles ne seront jamais nettoyées,
et elles causeront des fuites de mémoire.
La raison pour laquelle les racines possibles sont enregistrées dans le tampon
même si le mécanisme est désactivé est qu'il aurait été trop coûteux de vérifier l'activation
éventuelle du mécanisme à chaque tentative d'ajout d'une racine dans le tampon. Le mécanisme
de ramasse-miettes et d'analyse peut, lui, être très coûteux en temps.
En plus de pouvoir changer la valeur du paramètre de configuration
zend.enable_gc, vous pouvez aussi activer ou désactiver le mécanisme de
ramasse-miettes en appelant les fonctions gc_enable() ou
gc_disable() respectivement. Utiliser ces fonctions aura le même effet que de
modifier le paramètre de configuration. Vous avez aussi la possibilité de forcer l'exécution du
ramasse-miettes à un moment donné dans votre script, même si le tampon n'est pas encore
complètement plein. Utilisez pour cela la fonction gc_collect_cycles(),
qui retournera le nombre de cycles alors collectés.
Vous pouvez prendre le contrôle en désactivant le ramasse-miettes ou en le
forçant à passer à un moment donné car certaines parties de votre application
pourraient être fortement dépendantes du temps de traitement, auquel cas vous pourriez
souhaiter que le ramasse-miettes ne se lance pas. Bien entendu, en désactivant le
ramasse-miettes pour certaines parties de votre application, vous prenez le risque de créer
des fuites de mémoire, puisque certaines racines probables pourraient ne pas être
enregistrées dans le tampon mémoire de taille limitée.
En conséquence, il est généralement recommandé de déclencher manuellement le processus grâce à
gc_collect_cycles() juste avant l'appel à
gc_disable(), pour libérer de la mémoire. Ceci laissera un tampon
vidé, et il y aura plus d'espace pour des racines probables lorsque
le mécanisme sera désactivé.