110 votes

Diagnostic des fuites de mémoire - Taille de mémoire autorisée de # octets épuisés

J'ai rencontré le redoutable message d'erreur, peut-être au prix d'efforts laborieux, PHP a manqué de mémoire :

La taille de mémoire autorisée de #### octets est épuisée (tentative d'allocation de #### octets) dans file.php à la ligne 123

Augmenter la limite

Si vous savez ce que vous faites et que vous voulez augmenter la limite, voir limite_mémoire :

ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit

Attention ! Vous pourriez ne résoudre que le symptôme et non le problème !

Diagnostic de la fuite :

Le message d'erreur indique une ligne à l'intérieur d'une boucle qui, à mon avis, fait fuir ou accumule inutilement de la mémoire. J'ai imprimé memory_get_usage() à la fin de chaque itération et on peut voir le nombre augmenter lentement jusqu'à ce qu'il atteigne la limite :

foreach ($users as $user) {
    $task = new Task;
    $task->run($user);
    unset($task); // Free the variable in an attempt to recover memory
    print memory_get_usage(true); // increases over time
}

Pour les besoins de cette question, supposons que le pire code spaghetti imaginable se cache dans le global-scope quelque part dans $user o Task .

Quels sont les outils, les astuces PHP ou les trucs de débogage qui peuvent m'aider à trouver et à résoudre le problème ?

53voto

troelskn Points 51966

PHP n'a pas de ramasse-miettes. Il utilise le comptage de références pour gérer la mémoire. Ainsi, la source la plus commune de fuites de mémoire sont les références cycliques et les variables globales. Si vous utilisez un framework, vous aurez beaucoup de code à parcourir pour les trouver, j'en ai peur. L'instrument le plus simple consiste à placer sélectivement les appels à memory_get_usage et de réduire à l'endroit où le code fuit. Vous pouvez également utiliser xdebug pour créer une trace du code. Exécutez le code avec traces d'exécution y show_mem_delta .

8voto

patcoll Points 680

J'ai remarqué une fois dans un vieux script que PHP maintenait la variable "as" comme étant en portée même après ma boucle foreach. Par exemple,

foreach($users as $user){
  $user->doSomething();
}
var_dump($user); // would output the data from the last $user 

Je ne sais pas si les futures versions de PHP ont corrigé ce problème ou non, puisque je l'ai vu. Si c'est le cas, vous pouvez unset($user) après le doSomething() pour l'effacer de la mémoire. YMMV.

6voto

kingoleg Points 162

Il y a plusieurs points possibles de fuite de mémoire en php :

  • php lui-même
  • extension php
  • la bibliothèque php que vous utilisez
  • votre code php

Il est assez difficile de trouver et de corriger les 3 premiers sans une profonde rétro-ingénierie ou une connaissance du code source de php. Pour le dernier, vous pouvez utiliser la recherche binaire pour le code de fuite de mémoire avec mémoire_get_usage

6voto

Nate Flink Points 1280

J'ai récemment rencontré ce problème sur une application, dans ce que je crois être des circonstances similaires. Un script qui s'exécute dans le cli de PHP qui boucle sur de nombreuses itérations. Mon script dépend de plusieurs bibliothèques sous-jacentes. Je soupçonne qu'une bibliothèque particulière en est la cause et j'ai passé plusieurs heures en vain à essayer d'ajouter des méthodes de destruction appropriées à ses classes, sans succès. Confronté à un long processus de conversion vers une autre bibliothèque (qui pourrait s'avérer avoir les mêmes problèmes), j'ai trouvé un contournement grossier du problème dans mon cas.

Dans ma situation, sur un cli linux, je bouclais sur un tas d'enregistrements d'utilisateurs et pour chacun d'entre eux, je créais une nouvelle instance de plusieurs classes que j'avais créées. J'ai décidé d'essayer de créer les nouvelles instances des classes en utilisant la méthode exec de PHP afin que ces processus s'exécutent dans un "nouveau thread". Voici un exemple très simple de ce à quoi je fais référence :

foreach ($ids as $id) {
   $lines=array();
   exec("php ./path/to/my/classes.php $id", $lines);
   foreach ($lines as $line) { echo $line."\n"; } //display some output
}

Il est évident que cette approche a des limites et qu'il faut être conscient des dangers qu'elle comporte, car il serait facile de créer un travail de lapin, mais dans certains cas rares, elle peut aider à surmonter une situation difficile, jusqu'à ce qu'une meilleure solution soit trouvée, comme dans mon cas.

6voto

Gunnar Lium Points 1893

J'ai rencontré le même problème, et ma solution a été de remplacer foreach par un for régulier. Je ne suis pas sûr des spécificités, mais il semble que foreach crée une copie (ou en quelque sorte une nouvelle référence) de l'objet. En utilisant une boucle for régulière, vous accédez directement à l'objet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X