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. 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 noeud, 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_ROOT_BUFFER_MAX_ENTRIES 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 de 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é.