Gestion de la mémoire et récupération des déchets
Découvrez le fonctionnement des zvals, du comptage de références et du GC.
Gestion de la mémoire et récupération des déchets est une leçon PHP Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage PHP Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours PHP Academy comprend 4 leçons au total.
Comment PHP gère la mémoire
Les valeurs PHP sont stockées dans des zval, principalement gérées par le comptage des références, avec un collecteur de cycles pour détecter ce que le comptage des références ne peut pas récupérer. Comprendre ce mécanisme explique les fuites mémoire des processus de longue durée, le coût des copies et les situations où gc_collect_cycles() est utile.
Le zval
Un zval est le conteneur de toute valeur PHP : une étiquette de type accompagnée d'une union stockant la valeur. Les valeurs scalaires (entiers, nombres à virgule flottante, booléens) sont stockées par valeur dans le zval et ne sont NOT comptées par référence. Les types complexes (chaînes, tableaux, objets) pointent vers une structure comptée par référence sur le tas.
<?php
// debug_zval_refcount-style inspection via gc + count
$arr = [1, 2, 3];
$copy = $arr; // refcount of the array bumps, no data copy yet (CoW)
// Modifying triggers copy-on-write separation
$copy[] = 4;
var_dump($arr); // still [1,2,3]
var_dump($copy); // [1,2,3,4]
?>Comptage des références
Chaque valeur gérée par comptage des références enregistre le nombre de zval qui pointent vers elle. Une affectation incrémente ce compteur ; la suppression ou la sortie de portée le décrémente. Lorsque le compteur atteint zéro, la mémoire est immédiatement libérée — sans attendre un passage du collecteur de cycles. La majeure partie de la mémoire PHP est récupérée de cette manière.
<?php
$a = str_repeat('x', 1000); // refcount 1 on the string
$b = $a; // refcount 2
unset($a); // refcount 1
unset($b); // refcount 0 -> freed instantly
echo "Refcounting frees deterministically\n";
?>Copie à l'écriture
Une affectation ne copie pas les données : elle les partage et augmente le compteur de références (copie à l'écriture). La duplication réelle n'a lieu qu'à la première écriture dans une valeur partagée. Cela rend le passage de grands tableaux peu coûteux tant qu'ils ne sont pas modifiés.
<?php
$big = range(1, 100000);
$shared = $big; // O(1): shared, refcount++
// Reads stay shared and cheap
echo count($shared), PHP_EOL;
// First write separates (copies) — now it costs O(n)
$shared[0] = -1;
echo $big[0], ' vs ', $shared[0], PHP_EOL; // 1 vs -1
?>Le problème : les cycles
Le comptage des références seul ne peut pas libérer les cycles de références. Si l'objet A contient une référence vers B et que B en contient une vers A, leurs compteurs n'atteignent jamais zéro, même après la suppression de toutes les variables externes : la mémoire fuit jusqu'à l'exécution du collecteur de cycles.
<?php
class Node { public ?Node $ref = null; }
$a = new Node();
$b = new Node();
$a->ref = $b; // a -> b
$b->ref = $a; // b -> a (cycle!)
unset($a, $b); // external refs gone, but internal cycle keeps refcount > 0
// The objects are unreachable but NOT yet freed by refcounting alone.
echo gc_collect_cycles() . " cycles collected\n";
?>Le collecteur de cycles
Le collecteur de cycles de PHP met en œuvre un marquage-suppression synchrone sur les racines. Les racines susceptibles de former un cycle (valeurs dont le compteur de références a été décrémenté sans atteindre zéro) sont mises en mémoire tampon. Lorsque le tampon des racines est plein (10 000 par défaut), le collecteur s'exécute : il simule les décréments, trouve les groupes réellement inaccessibles et les libère.
<?php
// GC status exposes the root buffer and run stats
for ($i = 0; $i < 3; $i++) {
$a = new stdClass();
$b = new stdClass();
$a->b = $b; $b->a = $a; // create a cycle
unset($a, $b); // becomes a GC root
}
print_r(gc_status());
?>Contrôler le collecteur de cycles
Vous pouvez piloter le collecteur : gc_disable()/gc_enable() permettent de le désactiver ou de l'activer, et gc_collect_cycles() force un passage et renvoie le nombre de valeurs libérées. Dans les traitements par lots où le débit est essentiel, il arrive que l'on désactive le collecteur pendant une phase intensive, puis qu'on l'exécute une seule fois à la fin.
<?php
gc_disable(); // no automatic cycle collection
// ... heavy phase that creates and drops many cycles ...
for ($i = 0; $i < 1000; $i++) {
$x = new stdClass(); $y = new stdClass();
$x->y = $y; $y->x = $x; unset($x, $y);
}
$freed = gc_collect_cycles(); // reclaim all at once
gc_enable();
echo "Freed $freed objects\n";
?>Processus de longue durée
Dans les démons CLI, les consommateurs de files d'attente et les processus Swoole/RoadRunner, la mémoire n'est pas récupérée lors de la fin d'une requête : le processus reste actif pendant des heures. Surveillez notamment les caches statiques qui croissent sans limite, les cycles qui s'accumulent et les ressources non fermées. Contrôlez l'utilisation avec memory_get_usage(true) et redémarrez régulièrement les processus.
<?php
$peakStart = memory_get_usage(true);
// Simulate processing a batch of jobs
for ($job = 0; $job < 5; $job++) {
$payload = range(0, 10000);
// ... process ...
unset($payload);
}
printf("start: %d KB, peak: %d KB, now: %d KB\n",
$peakStart >> 10,
memory_get_peak_usage(true) >> 10,
memory_get_usage(true) >> 10
);
?>WeakReference et WeakMap
Pour mettre en cache des données ou les associer à un objet sans le maintenir en vie, utilisez WeakReference (8.0) et WeakMap (8.0). Une entrée de WeakMap n'incrémente pas le compteur de références de l'objet clé ; l'objet peut donc être récupéré et son entrée supprimée automatiquement — ce qui est idéal pour les tables auxiliaires de métadonnées qui ne doivent pas provoquer de fuite.
<?php
$map = new WeakMap();
$obj = new stdClass();
$map[$obj] = 'metadata'; // does NOT keep $obj alive
echo count($map) . PHP_EOL; // 1
unset($obj); // object collectible -> entry removed
echo count($map) . PHP_EOL; // 0
?>Chaînes internées et valeurs immuables
Les chaînes littérales de votre code source sont internées : elles sont stockées une seule fois, partagées et ni soumises au comptage des références ni libérées pendant la requête. OPcache place les chaînes internées et les tableaux de codes opérationnels immuables (mis en cache) dans la mémoire partagée entre les requêtes. C'est pourquoi la copie des chaînes littérales est pratiquement gratuite.
Limite mémoire et allocateur
PHP utilise son propre allocateur emalloc (gestionnaire de mémoire Zend), superposé à l'allocateur du système, et comptabilise l'utilisation par requête par rapport à memory_limit. Atteindre la limite déclenche une erreur fatale. La valeur true dans memory_get_usage(true) indique les blocs réellement alloués par l'OS, et non uniquement ce que votre script détient actuellement.
<?php
echo 'limit: ' . ini_get('memory_limit') . PHP_EOL;
echo 'emalloc usage: ' . memory_get_usage() . " bytes\n"; // ZendMM tracked
echo 'real usage: ' . memory_get_usage(true) . " bytes\n"; // from OS
?>Vérification rapide
Pourquoi le comptage des références seul peut-il provoquer une fuite pour certains objets ?
Récapitulatif
PHP stocke les valeurs dans des zval ; les valeurs scalaires sont stockées par valeur, tandis que les types complexes sont alloués sur le tas et comptés par référence avec une copie à l'écriture. Le comptage des références libère la mémoire de manière déterministe lorsque le compteur atteint zéro, mais ne peut pas récupérer les cycles ; un collecteur synchrone par marquage et suppression s'exécute donc lorsque le tampon des racines est plein (ou via gc_collect_cycles()). Utilisez WeakMap/WeakReference pour les associations non propriétaires et surveillez la croissance sans limite des processus de longue durée.
Questions Fréquemment Posées
La leçon « Gestion de la mémoire et récupération des déchets » est-elle gratuite ?
Oui — le texte complet de « Gestion de la mémoire et récupération des déchets » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours PHP Academy, passe à CoddyKit PRO. Le cours PHP Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Gestion de la mémoire et récupération des déchets » ?
Découvrez le fonctionnement des zvals, du comptage de références et du GC. Tu pratiques PHP Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer PHP Academy ?
Aucune expérience préalable n'est requise. PHP Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.
Combien de temps prend la leçon « Gestion de la mémoire et récupération des déchets » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon PHP Academy ?
Oui. Chaque leçon PHP Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Fonctionnement du moteur Zend
- Gestion de la mémoire et récupération des déchets
- OPcache et compilation JIT
- Écrire une extension PHP de base en C