J'ai rencontré le message d'erreur redouté, probablement grâce à un effort minutieux, PHP a manqué de mémoire:
Taille de mémoire autorisée de #### octets épuisés (tentative d'allocation de #### octets) dans file.php à la ligne 123
Augmenter la limite
Si vous savez ce que vous faites et que vous souhaitez augmenter la limite, consultez memory_limit :
ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
Il faut se méfier! Vous ne résolvez peut-être que le symptôme et non le problème!
Diagnostiquer la fuite:
Le message d'erreur pointe vers une ligne avec une boucle que je pense être une fuite ou une accumulation inutile de mémoire. J'ai imprimé des memory_get_usage()
déclarations à la fin de chaque itération et je peux 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 une portée globale quelque part dans $user
ou Task
.
Quels outils, astuces PHP ou débogage vaudou peuvent m'aider à trouver et à résoudre le problème?
la source
Réponses:
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 courante 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 le trouver, j'en ai peur. L'instrument le plus simple consiste à effectuer des appels de manière sélective
memory_get_usage
et à les 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 les traces d'exécution etshow_mem_delta
.la source
Voici une astuce que nous avons utilisée pour identifier les scripts qui utilisent le plus de mémoire sur notre serveur.
Enregistrez l'extrait suivant dans un fichier, par exemple
/usr/local/lib/php/strangecode_log_memory_usage.inc.php
:Utilisez-le en ajoutant ce qui suit à httpd.conf:
Ensuite, analysez le fichier journal à
/var/log/httpd/php_memory_log
Vous devrez peut-être le faire
touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
avant que votre utilisateur Web puisse écrire dans le fichier journal.la source
J'ai remarqué une fois dans un ancien script que PHP maintiendrait la variable "as" comme dans la portée même après ma boucle foreach. Par exemple,
Je ne sais pas si les futures versions de PHP ont corrigé cela ou non depuis que je l'ai vu. Si tel est le cas, vous pouvez
unset($user)
après ladoSomething()
ligne pour l'effacer de la mémoire. YMMV.la source
unset()
faire, mais gardez à l'esprit que pour les objets, tout ce que vous faites est de changer l'endroit où votre variable pointe - vous ne l'avez pas réellement supprimée de la mémoire. PHP libérera automatiquement la mémoire une fois qu'elle est hors de portée de toute façon, donc la meilleure solution (en termes de cette réponse, pas de la question de l'OP) est d'utiliser des fonctions courtes afin qu'elles ne s'accrochent pas à cette variable de la boucle aussi longue.Il y a plusieurs points possibles de fuite de mémoire en php:
Il est assez difficile de trouver et de corriger les 3 premiers sans rétro-ingénierie approfondie ou connaissance du code source php. Pour le dernier, vous pouvez utiliser la recherche binaire pour le code de fuite de mémoire avec memory_get_usage
la source
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 en vain. Confronté à un long processus de conversion vers une bibliothèque différente (qui pourrait se révéler avoir les mêmes problèmes), j'ai trouvé un moyen de contourner le 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 échantillon vraiment basique de ce à quoi je fais référence:
De toute évidence, cette approche a des limites, et il faut être conscient des dangers de cela, car il serait facile de créer un travail de lapin, mais dans de rares cas, cela pourrait aider à surmonter une situation difficile, jusqu'à ce qu'une meilleure solution puisse être trouvée. , comme dans mon cas.
la source
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 détails, mais il semble que foreach crée une copie (ou en quelque sorte une nouvelle référence) à l'objet. En utilisant une boucle for régulière, vous accédez directement à l'élément.
la source
Je vous suggère de vérifier le manuel php ou d'ajouter la
gc_enable()
fonction pour collecter les déchets ... C'est-à-dire que les fuites de mémoire n'affectent pas la façon dont votre code fonctionne.PS: php a un garbage collector
gc_enable()
qui ne prend aucun argument.la source
J'ai récemment remarqué que les fonctions lambda de PHP 5.3 laissent de la mémoire supplémentaire utilisée lorsqu'elles sont supprimées.
Je ne sais pas pourquoi, mais cela semble prendre 250 octets supplémentaires chaque lambda même après la suppression de la fonction.
la source
Si ce que vous dites à propos de PHP ne faisant GC qu'après une fonction est vrai, vous pouvez envelopper le contenu de la boucle dans une fonction comme solution de contournement / expérience.
la source
run()
qui est appelé est aussi une fonction, à la fin de laquelle le GC devrait se produire.Un énorme problème que j'ai eu était d'utiliser create_function . Comme dans les fonctions lambda, il laisse le nom temporaire généré en mémoire.
Une autre cause de fuites de mémoire (dans le cas de Zend Framework) est le Zend_Db_Profiler. Assurez-vous que cela est désactivé si vous exécutez des scripts sous Zend Framework. Par exemple, j'ai eu dans mon application.ini le suivant:
Exécuter environ 25 000 requêtes + des charges de traitement avant cela, a amené la mémoire à 128 Mo (ma limite de mémoire maximale).
En réglant simplement:
il suffisait de le garder sous 20 Mo
Et ce script fonctionnait dans CLI, mais il instanciait la Zend_Application et exécutait le Bootstrap, donc il utilisait la configuration de "développement".
Cela a vraiment aidé à exécuter le script avec le profilage xDebug
la source
Je ne l'ai pas vu explicitement mentionné, mais xdebug fait un excellent travail de profilage du temps et de la mémoire (à partir de 2.6 ). Vous pouvez prendre les informations qu'il génère et les transmettre à une interface graphique de votre choix: webgrind (time only), kcachegrind , qcachegrind ou autres et il génère des arbres d'appels et des graphiques très utiles pour vous permettre de trouver les sources de vos différents problèmes .
Exemple (de qcachegrind):
la source
Je suis un peu en retard pour cette conversation mais je vais partager quelque chose de pertinent pour Zend Framework.
J'ai eu un problème de fuite de mémoire après avoir installé php 5.3.8 (en utilisant phpfarm) pour travailler avec une application ZF qui a été développée avec php 5.2.9. J'ai découvert que la fuite de mémoire était déclenchée dans le fichier httpd.conf d'Apache, dans ma définition d'hôte virtuel, où il est dit
SetEnv APPLICATION_ENV "development"
. Après avoir commenté cette ligne, les fuites de mémoire se sont arrêtées. J'essaie de trouver une solution de contournement en ligne dans mon script php (principalement en le définissant manuellement dans le fichier principal index.php).la source
"development"
environnement a généralement un tas de journalisation et de profilage que d'autres environnements peuvent ne pas avoir. Le commentaire de la ligne a simplement fait que votre application utilise à la place l'environnement par défaut, qui est généralement"production"
ou"prod"
. La fuite de mémoire existe toujours; le code qui le contient n'est tout simplement pas appelé dans cet environnement.Je ne l'ai pas vu mentionné ici, mais une chose qui pourrait être utile est d'utiliser xdebug et xdebug_debug_zval ('variableName') pour voir le refcount.
Je peux également fournir un exemple d'une extension php gênant: le Z-Ray de Zend Server. Si la collecte de données est activée, l'utilisation de la mémoire apparaîtra à chaque itération, comme si la collecte des déchets était désactivée.
la source