Ich bin auf die gefürchtete Fehlermeldung gestoßen, möglicherweise durch mühsame Bemühungen, PHP hat keinen Speicher mehr:
Zulässige Speichergröße von #### Bytes erschöpft (versucht, #### Bytes zuzuweisen) in file.php in Zeile 123
Das Limit erhöhen
Wenn Sie wissen, was Sie tun und das Limit erhöhen möchten, lesen Sie memory_limit :
ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
In acht nehmen! Möglicherweise lösen Sie nur das Symptom und nicht das Problem!
Diagnose des Lecks:
Die Fehlermeldung zeigt auf eine Zeile mit einer Schleife, von der ich glaube, dass sie Speicher verliert oder sich unnötig ansammelt. Ich habe memory_get_usage()
am Ende jeder Iteration Anweisungen gedruckt und kann sehen, dass die Anzahl langsam wächst, bis sie das Limit erreicht:
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
}
Für die Zwecke dieser Frage nehmen wir an, dass sich der schlimmste vorstellbare Spaghetti-Code irgendwo in $user
oder im globalen Bereich versteckt Task
.
Welche Tools, PHP-Tricks oder das Debuggen von Voodoo können mir helfen, das Problem zu finden und zu beheben?
quelle
Antworten:
PHP hat keinen Garbage Collector. Es verwendet die Referenzzählung, um den Speicher zu verwalten. Daher sind zyklische Referenzen und globale Variablen die häufigste Ursache für Speicherverluste. Wenn Sie ein Framework verwenden, müssen Sie leider viel Code durchsuchen, um es zu finden. Das einfachste Instrument besteht darin, selektiv Anrufe zu tätigen
memory_get_usage
und auf die Stelle einzugrenzen , an der der Code leckt. Sie können auch xdebug verwenden , um eine Ablaufverfolgung des Codes zu erstellen. Führen Sie den Code mit Ausführungsspuren und ausshow_mem_delta
.quelle
Hier ist ein Trick, mit dem wir ermittelt haben, welche Skripte den meisten Speicher auf unserem Server belegen.
Speichern Sie den folgenden Code - Schnipsel in einer Datei an, zB
/usr/local/lib/php/strangecode_log_memory_usage.inc.php
:Setzen Sie es ein, indem Sie Folgendes zu httpd.conf hinzufügen:
Analysieren Sie dann die Protokolldatei unter
/var/log/httpd/php_memory_log
Möglicherweise müssen Sie dies tun,
touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
bevor Ihr Webbenutzer in die Protokolldatei schreiben kann.quelle
Ich habe einmal in einem alten Skript bemerkt, dass PHP die Variable "as" auch nach meiner foreach-Schleife wie im Gültigkeitsbereich beibehalten würde. Beispielsweise,
Ich bin mir nicht sicher, ob zukünftige PHP-Versionen dies behoben haben oder nicht, seit ich es gesehen habe. Wenn dies der Fall ist, können Sie
unset($user)
nach derdoSomething()
Zeile aus dem Speicher löschen. YMMV.quelle
unset()
es tun, aber denken Sie daran, dass Sie für Objekte nur ändern, wohin Ihre Variable zeigt - Sie haben sie nicht tatsächlich aus dem Speicher entfernt. PHP gibt den Speicher automatisch frei, sobald er ohnehin außerhalb des Gültigkeitsbereichs liegt. Die bessere Lösung (in Bezug auf diese Antwort, nicht die Frage des OP) besteht darin, kurze Funktionen zu verwenden, damit sie nicht auch an dieser Variablen aus der Schleife hängen lange.Es gibt mehrere mögliche Speicherpunkte in PHP:
Es ist ziemlich schwierig, die ersten 3 ohne tiefes Reverse Engineering oder PHP-Quellcode-Kenntnisse zu finden und zu reparieren. Für den letzten können Sie die binäre Suche nach Speicherleckcode mit memory_get_usage verwenden
quelle
Ich bin kürzlich in einer Anwendung auf dieses Problem gestoßen, unter ähnlichen Umständen. Ein Skript, das in PHPs CLI ausgeführt wird und viele Iterationen durchläuft. Mein Skript hängt von mehreren zugrunde liegenden Bibliotheken ab. Ich vermute, dass eine bestimmte Bibliothek die Ursache ist, und ich habe mehrere Stunden vergeblich versucht, ihren Klassen geeignete Zerstörungsmethoden hinzuzufügen, ohne Erfolg. Angesichts eines langwierigen Konvertierungsprozesses in eine andere Bibliothek (der sich als dieselben Probleme herausstellen könnte) habe ich eine grobe Lösung für das Problem in meinem Fall gefunden.
In meiner Situation habe ich unter einer Linux-Cli eine Reihe von Benutzerdatensätzen durchlaufen und für jeden von ihnen eine neue Instanz mehrerer von mir erstellter Klassen erstellt. Ich beschloss, die neuen Instanzen der Klassen mit der exec-Methode von PHP zu erstellen, damit diese Prozesse in einem "neuen Thread" ausgeführt werden. Hier ist ein wirklich grundlegendes Beispiel dessen, worauf ich mich beziehe:
Offensichtlich weist dieser Ansatz Einschränkungen auf, und man muss sich der Gefahren bewusst sein, da es einfach wäre, einen Kaninchenjob zu schaffen. In einigen seltenen Fällen kann es jedoch hilfreich sein, eine schwierige Situation zu überwinden, bis eine bessere Lösung gefunden werden kann wie in meinem Fall.
quelle
Ich bin auf das gleiche Problem gestoßen, und meine Lösung bestand darin, foreach durch ein reguläres für zu ersetzen. Ich bin mir über die Einzelheiten nicht sicher, aber es scheint, als würde foreach eine Kopie (oder irgendwie einen neuen Verweis) auf das Objekt erstellen. Mit einer regulären for-Schleife greifen Sie direkt auf das Element zu.
quelle
Ich würde vorschlagen, dass Sie das PHP-Handbuch überprüfen oder die
gc_enable()
Funktion zum Sammeln des Mülls hinzufügen ... Das heißt, die Speicherlecks haben keinen Einfluss darauf, wie Ihr Code ausgeführt wird.PS: PHP hat einen Garbage Collector
gc_enable()
, der keine Argumente akzeptiert.quelle
Ich habe kürzlich festgestellt, dass PHP 5.3 Lambda-Funktionen zusätzlichen Speicherplatz belassen, wenn sie entfernt werden.
Ich bin mir nicht sicher warum, aber es scheint zusätzliche 250 Bytes pro Lambda zu benötigen, selbst nachdem die Funktion entfernt wurde.
quelle
Wenn das, was Sie über PHP sagen, das GC erst nach einer Funktion ausführt, wahr ist, können Sie den Inhalt der Schleife als Problemumgehung / Experiment in eine Funktion einschließen.
quelle
run()
, was aufgerufen wird, auch eine Funktion, an deren Ende der GC stattfinden soll.Ein großes Problem war die Verwendung von create_function . Wie bei Lambda-Funktionen bleibt der generierte temporäre Name im Speicher.
Eine weitere Ursache für Speicherverluste (im Fall von Zend Framework) ist der Zend_Db_Profiler. Stellen Sie sicher, dass dies deaktiviert ist, wenn Sie Skripts unter Zend Framework ausführen. Zum Beispiel hatte ich in meiner application.ini Folgendes:
Das Ausführen von ungefähr 25.000 Abfragen + einer Menge Verarbeitung zuvor brachte den Speicher auf nette 128 MB (mein maximales Speicherlimit).
Durch einfaches Einstellen:
es war genug, um es unter 20 Mb zu halten
Dieses Skript wurde in der CLI ausgeführt, aber es instanziierte die Zend_Application und führte den Bootstrap aus, sodass die Konfiguration "Entwicklung" verwendet wurde.
Es hat wirklich geholfen, das Skript mit xDebug-Profiling auszuführen
quelle
Ich habe es nicht explizit erwähnt gesehen, aber xdebug leistet hervorragende Arbeit bei der Profilerstellung von Zeit und Speicher (ab 2.6 ). Sie können die generierten Informationen an ein GUI-Frontend Ihrer Wahl weitergeben: Webgrind (nur Zeit), Kcachegrind , Qcachegrind oder andere. sehr nützliche Ihrer verschiedenen Probleme finden können .
Beispiel (von qcachegrind):
quelle
Ich bin etwas spät dran, aber ich werde etwas mitteilen, das für Zend Framework relevant ist.
Ich hatte ein Speicherverlustproblem nach der Installation von PHP 5.3.8 (mit Phpfarm), um mit einer ZF-App zu arbeiten, die mit PHP 5.2.9 entwickelt wurde. Ich habe festgestellt, dass der Speicherverlust in der Datei httpd.conf von Apache in meiner virtuellen Hostdefinition ausgelöst wurde
SetEnv APPLICATION_ENV "development"
. Nach dem Kommentieren dieser Zeile wurden die Speicherlecks gestoppt. Ich versuche, eine Inline-Problemumgehung in meinem PHP-Skript zu finden (hauptsächlich durch manuelles Definieren in der Hauptdatei index.php).quelle
"development"
Umgebung verfügt normalerweise über eine Reihe von Protokollierungen und Profilen, die andere Umgebungen möglicherweise nicht haben. Durch das Kommentieren des Line-Outs hat Ihre Anwendung stattdessen die Standardumgebung verwendet, die normalerweise"production"
oder ist"prod"
. Das Speicherleck besteht noch; Der Code, der ihn enthält, wird in dieser Umgebung einfach nicht aufgerufen.Ich habe es hier nicht erwähnt gesehen, aber eine Sache, die hilfreich sein könnte, ist die Verwendung von xdebug und xdebug_debug_zval ('variableName'), um den Refcount zu sehen.
Ich kann auch ein Beispiel für eine PHP-Erweiterung geben, die im Weg steht: Zend Ray von Zend Server. Wenn die Datenerfassung aktiviert ist, wird die Speichernutzung bei jeder Iteration erhöht, als ob die Speicherbereinigung deaktiviert wäre.
quelle