gibt's da best practices, die man bei PHP Dauerläufern einhalten muss, um den Heap nicht volllaufen zu lassen?
Der Heap ist nur eine mögliche Ursache für einen Programmabsturz, es gibt noch andere Möglichkeiten:
Beginnend mit dem offensichtlichen: Das Programm darf nicht regulär terminieren, man braucht irgendwo eine Endlosschleife oder eine Endlosrekursion. Das mit der Endlosrekursion ist nicht ganz einfach, weil eine naive Endlosrekursion zu einem Stackoverflow führen würde. Tail-rekursive Funktionen haben diesen Nachteil theoretisch nicht, weil jeder rekursive Aufruf den zuletzt verwendeten Stackframe wieder verwenden kann. Manche Laufzeitsysteme können das automatisch optimieren, PHP macht das, soweit ich weiß, aber nicht. Es gibt Workarounds, wie Trampolining, die diese Limitierung umgehen, aber das ist nicht ganz einfach. Im Falle von PHP würde ich eher zur guten alten Endlosschleife raten.
Dann gibt es diverse Möglichkeiten, wie das Programm abnormal terminieren könnte. Ein break
, die
oder exit
innerhalb der Endlosschleife hätte das zur Folge. Das lässt sich aber relativ einfach vermeiden.
Nicht abgefangene Exceptions
würden ebenfalls das Programmm abnormal terminieren. Das ist der wohl kritischste Punkt. Jede Anweisung, die eine Exception
zur Folge haben kann, muss in einem try/catch
-Block stattfinden und der catch
-Block muss so getypt sein, dass er auch alle möglichen Fehler abfängt. Insbesondere muss man hier an Typfehler denken und dann gibt es leider auch ein paar Laufzeit-Fehler, die man in PHP überhaupt nicht abfangen kann. declare(strict_types=1)
, Linter und andere Code-Analyse-Tools können hier helfen.
Wie im ersten Punkt schon erwähnt muss man sich vor Stapelüberlaufen schützen, das ist kompliziert in den Griff zu kriegen. Ein Stackoverflow tritt nicht nur bei Endlosrekursionen auf, sondern auch bei Rekursionen, die eigentlich gegen einen Basisfall konvergieren und sogar in nicht rekursiven Funktionsaufrufen, wenn die Verschatelungstiefe zu groß wird.
Und, um auf deine ursprüngliche Frage einzugehen: Der Heap darf nicht unbeschränkt wachsen, sondern muss durch eine obere Schranke begrenzt sein. Die Garbage-Collection hilft dabei, unset
kann die GC unterstützen, ist aber kein Allheilmittel. unset
gibt keinen Speicher selbständig frei, sondern teilt der GC nur mit, dass sie der Variablen nicht folgen muss. Das ist nur selten nötig: Nur bei globalen Variablen oder Variablen, die durch Closures gecaptured worden sind. Lokale Variablen, werden beim Funktionsrücksprung automatisch gelöscht und die GC weiß, dass sie ihnen nicht folgen muss. Hilfe kann außerdem eine WeakRef bringen, das ist ist eine Referenz, der die GC automatisch nicht folgt. Das heißt, das der referenzierte Speicher aufgeräumt wird, wenn es nur noch schwache Referenzen darauf gibt. Schlimmer für die GC als Variablen sind Objekt-Eigenschaften. Es gibt in PHP eine Datenstruktur WeakMap, die ebenfalls nur schwache Referenzen auf die gespeicherten Werte enhält. Beide Datenstrukturen sind leider nicht Teil des Standard-Sprachumfangs von PHP, sondern erfordern eine Erweiterung. Außerdem ist eine WeakRef kein Drop-In-Replacement für eine Variable, Gleiches gilt für WeakRef und Objekte und Arrays, man muss sich vorher Gedanken machen, ob das wirklich gewollt ist. Im Zweifel zählt Korrektheit meistens mehr als Speicherbedarf.
Ein schlimmeres Problem ist, dass auch wenn so eine obere Speicherschranke für die Heap-Größe exisitiert, das Programm weiterhin in Folge zu wenig freien Speichers abstürzen kann. Zum Beispiel wenn das System gerade anderweitig so viel Speicher benutzt, dass der PHP-Prozess schlicht keinen weiteren Speicher allozieren kann. Einige Laufzeit-Systeme können eingeschränkt dagegen schützen, indem sie beim Starten der virtuellen Maschine oder des Interpreters große Blöck Speicher alloziieren, die garantiert während der gesamten Laufzeit zur Verfügung stehen. Ganz darauf verlassen kann man sich aber auch nicht.
Am besten man wappnet sich immer für den Fall, dass doch mal was schief läuft: Unter Linux kann man einen Prozess-Supervisor einsetzen, der abgesürzte Prozesse automatisch neustartet. Der Supervisor sollte auch nach einem Systemneustart automatisch wieder hochfahren, das geht bswp. mit einem @reboot
-Cronjob. Und letztlich sollte man sich auch mal Gedanken über Hardware-Ausfälle, Stromausfälle oder Netzwerk-Ausfälle gemacht haben.
Wenn ich noch einen Rat geben darf: Es gibt Sprachen, die sind mehr auf Ausfallsicherheit ausgelegt als PHP. Wenn es nicht nur um risikolose Haushaltsarbeit auf einem Webserver geht, sondern um kritische Anwendungen, bei denen ein Programmabsturz schwere Folgen haben könnte, dann bedient man sich besser bei diesen Werkzeugen.