Heim > Artikel > Backend-Entwicklung > PHP-Garbage-Collection-Mechanismus – Grundkenntnisse der Referenzzählung
Jede PHP-Variable existiert in einem Variablencontainer namens „zval“. Ein zval-Variablencontainer enthält zusätzlich zum Typ und Wert der Variablen zwei Bytes zusätzlicher Informationen. Der erste ist „is_ref“, ein Bool-Wert, der verwendet wird, um zu identifizieren, ob diese Variable zum Referenzsatz gehört. Durch dieses Byte kann die PHP-Engine gewöhnliche Variablen von Referenzvariablen unterscheiden. Da PHP Benutzern die Verwendung benutzerdefinierter Referenzen durch die Verwendung von & ermöglicht, gibt es im zval-Variablencontainer auch einen internen Referenzzählmechanismus, um die Speichernutzung zu optimieren. Das zweite zusätzliche Byte ist „refcount“, das verwendet wird, um die Anzahl der Variablen (auch Symbole genannt) anzugeben, die auf diesen zval-Variablencontainer verweisen. Alle Symbole existieren in einer Symboltabelle, wobei jedes Symbol einen Gültigkeitsbereich (Scope) hat, das Hauptskript (z. B. das über den Browser angeforderte Skript) und jede Funktion oder Methode ebenfalls einen Gültigkeitsbereich haben.
Wenn einer Variablen ein konstanter Wert zugewiesen wird, wird ein Zval-Variablencontainer generiert, wie im folgenden Beispiel:
Beispiel #1 Erstellen Sie einen neuen Zval-Container
<?php $a = "new string"; ?>
Im obigen Beispiel wird die neue Variable a im aktuellen Gültigkeitsbereich generiert. Und es wird ein Variablencontainer vom Typ string und dem Wert new string generiert. In den zusätzlichen zwei Bytes an Informationen ist „is_ref“ standardmäßig auf FALSE gesetzt, da keine benutzerdefinierte Referenz generiert wird. „refcount“ ist auf 1 gesetzt, da es nur eine Variable gibt, die diesen Variablencontainer verwendet. Beachten Sie, dass „is_ref“ immer FALSE ist. Wenn Sie Xdebug installiert haben, können Sie zur Anzeige die Funktion xdebug_debug_zval() aufrufen die Werte von „refcount“ und „is_ref“.
Beispiel #2 Zval-Informationen anzeigen
<?php xdebug_debug_zval('a'); ?>
Die obige Routine gibt Folgendes aus:
a: (refcount=1, is_ref=0)='new string'
Das Zuweisen einer Variablen zu einer anderen Variable erhöht die Anzahl der Referenzen (Refcount). ).
Beispiel #3 Wachstum der Refcount in zval
<?php $a = "new string"; $b = $a; xdebug_debug_zval( 'a' ); ?>
Die obige Routine gibt Folgendes aus:
a: (refcount=2, is_ref=0)='new string'
Zu diesem Zeitpunkt beträgt die Anzahl der Referenzen 2 , da derselbe Variablencontainer A mit Variable a und Variable b verknüpft ist. PHP kopiert den generierten Variablencontainer nicht, wenn dies nicht erforderlich ist. Der Variablencontainer wird zerstört, wenn „refcount“ 0 wird. Wenn eine mit einem Variablencontainer verknüpfte Variable ihren Gültigkeitsbereich verlässt (z. B. wenn die Funktionsausführung endet) oder die Funktion unset() für die Variable aufgerufen wird, wird „refcount“ gelöscht um 1 reduziert werden, wie das folgende Beispiel veranschaulichen kann:
Beispiel Nr. 4 Reduzierung des Refcounts in zval
<?php $a = "new string"; $c = $b = $a; xdebug_debug_zval( 'a' ); unset( $b, $c ); xdebug_debug_zval( 'a' ); ?>
Die obige Routine gibt Folgendes aus:
a: (refcount=3, is_ref=0)='new string' a: (refcount=1, is_ref=0)='new string'
Wenn wir nun unset($a); ausführen, wird dieser Variablencontainer mit Typ und Wert aus dem Speicher gelöscht.
Zusammengesetzte Typen
Bei der Betrachtung zusammengesetzter Typen wie Array und Objekt sind die Dinge etwas komplizierter. Im Gegensatz zu skalaren (skalaren) Typwerten speichern Array- und Objekttypvariablen ihre Mitglieder oder Eigenschaften in ihren eigenen Symboltabellen. Das bedeutet, dass im folgenden Beispiel drei Zval-Variablencontainer generiert werden.
Beispiel #5 Erstellen Sie ein Array zval
<?php $a = array( 'meaning' => 'life', 'number' => 42 ); xdebug_debug_zval( 'a' ); ?>
Die Ausgabe der obigen Routine ist ähnlich wie:
a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=1, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42 )
Beispiel #6 Fügen Sie ein vorhandenes Element zum hinzu array In
<?php $a = array( 'meaning' => 'life', 'number' => 42 ); $a['life'] = $a['meaning']; xdebug_debug_zval( 'a' ); ?>
ähnelt die Ausgabe der obigen Routine:
a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=2, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42, 'life' => (refcount=2, is_ref=0)='life' )
oder wird grafisch wie folgt angezeigt:
Aus der obigen xdebug-Ausgabe Informationen, wir können sehen, dass das ursprüngliche Array-Element und das neu hinzugefügte Array-Element demselben zval-Variablencontainer von „refcount“ 2 zugeordnet sind. Obwohl die Ausgabe von Xdebug zeigt, dass die beiden zval-Variablencontainer den Wert „life“ haben sind eigentlich gleich. Die Funktion xdebug_debug_zval() zeigt diese Informationen nicht an, aber Sie können sie sehen, indem Sie die Speicherzeigerinformationen anzeigen.
Das Löschen eines Elements im Array ähnelt dem Löschen einer Variablen aus dem Bereich. Nach dem Löschen wird der „refcount“-Wert des Containers, in dem sich das Element im Array befindet, verringert der „refcount“ „Wenn“ 0 ist, wird der Variablencontainer aus dem Speicher gelöscht. Hier ist ein weiteres Beispiel zur Veranschaulichung:
Beispiel #7 Elemente aus dem Array entfernen
<?php $a = array( 'meaning' => 'life', 'number' => 42 ); $a['life'] = $a['meaning']; unset( $a['meaning'], $a['number'] ); xdebug_debug_zval( 'a' ); ?>
Die obige Routine Die Ausgabe sieht so aus:
a: (refcount=1, is_ref=0)=array ( 'life' => (refcount=1, is_ref=0)='life' )
Jetzt wird es interessant, wenn wir ein Array selbst als Element dieses Arrays hinzufügen, wie das nächste Beispiel veranschaulichen wird. Im Beispiel haben wir den Referenzoperator hinzugefügt, andernfalls generiert PHP eine Kopie.
Beispiel #8 Array-Elemente zum Array selbst hinzufügen
<?php $a = array( 'one' ); $a[] =& $a; xdebug_debug_zval( 'a' ); ?>
Die Ausgabe der obigen Routine ist ähnlich wie:
a: (refcount=2, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=... )
Oder grafisch
Sie können sehen, dass der „Refcount“ im Variablencontainer, auf den die Array-Variable (a) und das zweite Element (1) dieses Arrays zeigen, 2 ist. Das „…“ in der obigen Ausgabe zeigt an, dass eine rekursive Operation stattgefunden hat, was in diesem Fall offensichtlich bedeutet, dass das „…“ auf das ursprüngliche Array zeigt.
Wie zuvor wird beim Aufruf von unset für eine Variable das Symbol gelöscht und die Anzahl der Referenzen im Variablencontainer, auf den es verweist, wird ebenfalls um 1 reduziert. Wenn wir also „unset“ für die Variable $a aufrufen, nachdem wir den obigen Code ausgeführt haben, wird die Anzahl der Verweise auf den Variablencontainer, auf den die Variable $a und das Array-Element „1“ zeigen, um 1 von „2“ auf „1“ reduziert. . Das folgende Beispiel kann dies veranschaulichen:
Beispiel #9 $a zerstören
(refcount=1, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=... )
oder grafisch wie folgt darstellen:
Aufräumprobleme (Cleanup Problems)
Obwohl es in einem Bereich kein Symbol mehr gibt, das auf diese Struktur (also den Variablencontainer) zeigt, weil das Array-Element „1 „zeigt immer noch auf das Array selbst, sodass dieser Container nicht gelöscht werden kann. Da kein anderes Symbol darauf verweist, hat der Benutzer keine Möglichkeit, die Struktur zu löschen, was zu einem Speicherverlust führt. Glücklicherweise löscht PHP diese Datenstruktur am Ende der Anfrage, aber bevor PHP sie löscht, verbraucht sie viel Speicherplatz. Dies passiert häufig, wenn Sie einen Parsing-Algorithmus implementieren oder andere Dinge tun, z. B. wenn ein untergeordnetes Element auf sein übergeordnetes Element verweist. Natürlich kann die gleiche Situation bei Objekten auftreten, tatsächlich ist es jedoch wahrscheinlicher, dass sie bei Objekten auftritt, da auf Objekte immer implizit verwiesen wird.
Es ist in Ordnung, wenn die obige Situation nur ein- oder zweimal auftritt, aber wenn Speicherverluste tausende oder sogar hunderttausende Male auftreten, ist dies offensichtlich ein großes Problem. In Skripten mit langer Laufzeit, wie z. B. Daemons, die Anfragen selten beenden, oder großen Mengen in Unit-Tests, wenn Template-Komponenten der eZ-Komponentenbibliothek Unit-Tests unterzogen werden, wird letzteres (siehe) Bei großen Suiten in Unit-Tests wird es Probleme geben. Das wird der Fall sein erfordern 2 GB Arbeitsspeicher, und der durchschnittliche Testserver verfügt nicht über so viel Speicherplatz.