PHP 8.3.4 Released!

Grundlagen des Zählens von Referenzen

Eine PHP-Variable wird in einem Container namens "zval" gespeichert. Ein zval-Container enthält neben dem Typ und dem Wert der Variablen noch zwei weitere Informationen. Die erste heißt "is_ref" und ist ein boolescher Wert, der angibt, ob die Variable Teil einer "Referenzmenge" ist oder nicht. Mit diesem Bit kann die PHP-Engine zwischen normalen Variablen und Referenzen unterscheiden. Da PHP mit dem &-Operator erzeugte benutzerdefinierte Referenzen zulässt, hat ein zval-Container auch einen internen Mechanismus zum Zählen von Referenzen, um die Speichernutzung zu optimieren. Diese zweite zusätzliche Information wird "refcount" genannt und enthält die Anzahl der Variablennamen (auch Symbole genannt), die auf diesen einen zval-Container verweisen. Alle Symbole sind in einer Symboltabelle gespeichert, von denen es eine pro Bereich gibt. Es gibt einen Bereich für das Hauptskript (d. h. das vom Browser angeforderte), sowie einen für jede Funktion oder Methode.

Ein zval-Container wird erstellt, wenn eine neue Variable mit einem konstanten Wert erstellt wird, z. B.:

Beispiel #1 Erstellen eines neuen zval-Containers

<?php
$a
= "new string";
?>

In diesem Fall wird der neue Symbolname a im aktuellen Bereich erstellt und ein neuer Variablencontainer mit dem Typ string und dem Wert new string angelegt. Das Bit "is_ref" ist standardmäßig auf false gesetzt, weil noch keine benutzerdefinierte Referenz erstellt wurde. Der "refcount" ist auf 1 gesetzt, da es nur ein Symbol gibt, das diesen Variablencontainer verwendet. Es ist zu beachten, dass Referenzen (d. h. "is_ref" ist true) mit "refcount" 1 so behandelt werden, als ob sie keine Referenzen wären (d. h. als ob "is_ref" false wäre). Wenn » Xdebug installiert ist, kann xdebug_debug_zval() aufgerufen werden, um diese Informationen anzuzeigen.

Beispiel #2 Anzeigen von zval-Informationen

<?php
$a
= "new string";
xdebug_debug_zval('a');
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

a: (refcount=1, is_ref=0)='new string'

Wenn diese Variable einem anderen Variablennamen zugewiesen wird, erhöht sich der Referenzzähler.

Beispiel #3 Erhöhen des Referenzzählers eines Zval

<?php
$a
= "new string";
$b = $a;
xdebug_debug_zval( 'a' );
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

a: (refcount=2, is_ref=0)='new string'

Der Referenzzähler ist hier 2, weil derselbe Variablencontainer sowohl mit a als auch mit b verknüpft ist. PHP ist schlau genug, den eigentlichen Variablencontainer nicht zu kopieren, wenn es nicht notwendig ist. Variablencontainer werden zerstört, wenn der "refcount" Null erreicht. Der "refcount" wird um eins verringert, wenn ein Symbol, das mit dem Variablencontainer verbunden ist, den Gültigkeitsbereich verlässt (z. B. am Ende einer Funktion) oder wenn ein Symbol nicht mehr zugewiesen ist (z. B. durch den Aufruf von unset()). Das folgende Beispiel zeigt dies:

Beispiel #4 Verringern des zval-Referenzzählers

<?php
$a
= "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
$b = 42;
xdebug_debug_zval( 'a' );
unset(
$c );
xdebug_debug_zval( 'a' );
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

a: (refcount=3, is_ref=0)='new string'
a: (refcount=2, is_ref=0)='new string'
a: (refcount=1, is_ref=0)='new string'

Wenn nun unset($a); aufgerufen wird, wird der Variablencontainer einschließlich Typ und Wert aus dem Speicher entfernt.

Zusammengesetzte Typen

Etwas komplexer wird es bei zusammengesetzten Typen wie Arrays und Objekten. Im Gegensatz zu scalaren Werten speichern Arrays und Objekte ihre Eigenschaften in einer eigenen Symboltabelle. Das bedeutet, dass das folgende Beispiel drei zval-Container erzeugt:

Beispiel #5 Erstellen eines Array-Zvals

<?php
$a
= array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
?>

Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:

a: (refcount=1, is_ref=0)=array (
   'meaning' => (refcount=1, is_ref=0)='life',
   'number' => (refcount=1, is_ref=0)=42
)

Oder grafisch

Zvals für ein einfaches Array

Die drei zval-Container sind: a, meaning und number. Ähnliche Regeln gelten für das Erhöhen und Verringern von "refcounts". Im Folgenden fügen wir dem Array ein weiteres Element hinzu und setzen seinen Wert auf den Inhalt eines bereits vorhandenen Elements:

Beispiel #6 Hinzufügen eines bereits vorhandenen Elements zu einem Array

<?php
$a
= array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
xdebug_debug_zval( 'a' );
?>

Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:

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 grafisch

Zvals für ein einfaches Array mit einer Referenz

Aus der obigen Xdebug-Ausgabe geht hervor, dass sowohl das alte als auch das neue Array-Element nun auf einen zval-Container zeigen, dessen "refcount" 2 ist. Obwohl die Xdebug-Ausgabe zwei zval-Container mit dem Wert 'life' anzeigt, handelt es sich um denselben. Die Funktion xdebug_debug_zval() zeigt dies nicht an, aber man könnte es sehen, wenn man sich auch den Speicherzeiger anzeigen lässt.

Wenn ein Element aus dem Array entfernt wird, ist das so, als würde ein Symbol aus einem Bereich entfernt. Dadurch wird der "refcount" eines Containers, auf den ein Array-Element zeigt, verringert. Auch hier gilt: Wenn der "refcount" Null erreicht, wird der Variablencontainer aus dem Speicher entfernt. Ein weiteres Beispiel soll dies verdeutlichen:

Beispiel #7 Entfernen eines Elements aus einem Array

<?php
$a
= array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
unset(
$a['meaning'], $a['number'] );
xdebug_debug_zval( 'a' );
?>

Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:

a: (refcount=1, is_ref=0)=array (
   'life' => (refcount=1, is_ref=0)='life'
)

Interessant wird es nun, wenn das Array selbst als Element des Arrays hinzugefügt wird. Das geschieht im nächsten Beispiel, in dem außerdem ein Referenzoperator eingebaut wird, da PHP sonst eine Kopie erstellen würde:

Beispiel #8 Hinzufügen des Arrays als Element von sich selbst

<?php
$a
= array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );
?>

Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:

a: (refcount=2, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)='one',
   1 => (refcount=2, is_ref=1)=...
)

Oder grafisch

Zvals bei einem Array mit zyklischer Referenz

Wie man sieht, zeigen nun sowohl die Array-Variable (a) als auch das zweite Element (1) auf einen Variablencontainer, der einen "refcount" von 2 hat. Das "..." in der obigen Darstellung zeigt, dass es sich um eine Rekursion handelt, was natürlich in diesem Fall bedeutet, dass das "..." zurück auf das ursprüngliche Array zeigt.

Genau wie zuvor wird beim Aufheben einer Variablen das Symbol entfernt, und die Anzahl der Referenzen des Variablencontainers, auf den sie zeigt, wird um eins verringert. Wenn also die Variable $a nach der Ausführung des obigen Codes aufgehoben wird, wird die Anzahl der Referenzen des Variablencontainers, auf den $a und das Element "1" zeigen, um eins verringert, von "2" auf "1". Dies kann wie folgt dargestellt werden:

Beispiel #9 Aufheben von $a

(refcount=1, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)='one',
   1 => (refcount=1, is_ref=1)=...
)

Oder grafisch

Zvals nach dem Entfernen eines Arrays mit einer zyklischen Referenz, was das Speicherleck deutlich macht

Probleme bei der Bereinigung

Obwohl es in keinem Bereich mehr ein Symbol gibt, das auf diese Struktur zeigt, kann sie nicht bereinigt werden, weil das Array-Element "1" immer noch auf dasselbe Array zeigt. Da es kein externes Symbol gibt, das darauf verweist, gibt es für den Benutzer keine Möglichkeit, diese Struktur zu bereinigen, was zu einem Speicherleck führt. Glücklicherweise bereinigt PHP diese Datenstruktur am Ende der Anfrage, aber bis dahin nimmt sie wertvollen Speicherplatz in Anspruch. Diese Situation tritt häufig auf, wenn Parsing-Algorithmen oder andere Dinge implementiert werden, bei denen ein untergeordnetes Element auf ein "übergeordnetes" Element zurückverweist. Die gleiche Situation kann natürlich auch bei Objekten eintreten, wobei dies sogar wahrscheinlicher ist, da Objekte immer implizit per Referenz verwendet werden.

Das ist vielleicht kein Problem, wenn es nur ein- oder zweimal vorkommt, aber wenn es Tausende oder sogar Millionen solcher Speicherverluste gibt, fängt es natürlich an, zum Problem zu werden. Besonders problematisch ist dies bei lang laufenden Skripten, z. B. Daemons, bei denen die Anfrage im Grunde nie endet, oder bei großen Mengen von Unit-Tests. Letzteres führte zu Problemen bei der Ausführung der Unit-Tests für die Template-Komponente der eZ Components-Bibliothek. In einigen Fällen wurden über 2 GB Speicher benötigt, die der Testserver nicht zur Verfügung hatte.

add a note

User Contributed Notes 6 notes

up
15
Anonymous
9 years ago
There seems to be no way to inspect the reference count of a specific class variable but you can view the reference count of all variables in the current class instance with xdebug_debug_zval('this');
up
12
Anonymous
9 years ago
If a variable is not present in the current scope xdebug_debug_zval will return null.
up
8
shkarbatov at gmail dot com
6 years ago
Result of "Example #8 Adding the array itself as an element of it self" will be another for PHP7:

a: (refcount=2, is_ref=1)=array (
0 => (refcount=2, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)

insted of:
a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)

Internal value representation in PHP 7:
https://nikic.github.io/2015/05/05/Internal-value-representation-in-PHP-7-part-1.html
up
7
skymei at skymei dot cn
4 years ago
$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');

ouputs with PHP 7.3.12 (cli)

a: (interned, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1
up
4
yuri1308960477 at gmail dot com
5 years ago
my php versoin : HP 7.1.25 (cli) (built: Dec 7 2018 08:20:45) ( NTS )

$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');

output:

a: (refcount=2, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1

if $a is a string value, 'refcount' equal 2 by defalut.
up
0
chxt2011 at 163 dot com
4 years ago
my php version is PHP 7.1.6 (cli), when I run

$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');

it shows:
a: (refcount=0, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1
To Top