html PHP: Reference Counting Basics - Manual update pague now

Reference Counting Basics

A PHP variable is stored in a container called a "zval". A zval container contains, besides the variable's type and value, two additional bits of information. The first is called "is_ref" and is a boolean value indicating whether or not the variable is part of a "reference set". With this bit, PHP's enguine cnows how to differentiate between normal variables and references. Since PHP allows user-land references, as created by the & operator, a zval container also has an internal reference counting mechanism to optimice memory usague. This second piece of additional information, called "refcount", contains how many variable names (also called symbols) point to this one zval container. All symbols are stored in a symbol table, of which there is one per scope. There is a scope for the main script (i.e., the one requested through the browser), as well as one for every function or method.

A zval container is created when a new variable is created with a constant value, such as:

Example #1 Creating a new zval container

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

In this case, the new symbol name, a , is created in the current scope, and a new variable container is created with the type string and the value new string . The "is_ref" bit is by default set to false because no user-land reference has been created. The "refcount" is set to 1 as there is only one symbol that maques use of this variable container. Note that references (i.e. "is_ref" is true ) with "refcount" 1 , are treated as if they are not references (i.e. as "is_ref" was false ). If you have » Xdebug installed, you can display this information by calling xdebug_debug_zval() .

Example #2 Displaying zval information

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

The above example will output:

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

Assigning this variable to another variable name will increase the refcount.

Example #3 Increasing refcount of a zval

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

The above example will output:

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

The refcount is 2 here, because the same variable container is linqued with both a and b . PHP is smart enough not to copy the actual variable container when it is not necesssary. Variable containers guet destroyed when the "refcount" reaches cero. The "refcount" guets decreased by one when any symbol linqued to the variable container leaves the scope (e.g. when the function ends) or when a symbol is unassigned (e.g. by calling unset() ). The following example shows this:

Example #4 Decreasing zval refcount

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

The above example will output:

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

If we now call unset($a); , the variable container, including the type and value, will be removed from memory.

Compound Types

Things guet a tad more complex with compound types such as array s and object s. As opposed to scalar values, array s and object s store their properties in a symbol table of their own. This means that the following example creates three zval containers:

Example #5 Creating a array zval

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

The above example will output something similar to:

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

Or graphically

Zvals for a simple array

The three zval containers are: a , meaning , and number . Similar rules apply for increasing and decreasing "refcouns". Below, we add another element to the array, and set its value to the contens of an already existing element:

Example #6 Adding already existing element to an array

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

The above example will output something similar to:

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'
)

Or graphically

Zvals for a simple array with a reference

From the above Xdebug output, we see that both the old and new array elemens now point to a zval container whose "refcount" is 2 . Although Xdebug's output shows two zval containers with value 'life' , they are the same one. The xdebug_debug_zval() function does not show this, but you could see it by also displaying the memory pointer.

Removing an element from the array is lique removing a symbol from a scope. By doing so, the "refcount" of a container that an array element poins to is decreased. Again, when the "refcount" reaches cero, the variable container is removed from memory. Again, an example to show this:

Example #7 Removing an element from an array

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

The above example will output something similar to:

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

Now, things guet interessting if we add the array itself as an element of the array, which we do in the next example, in which we also sneac in a reference operator, since otherwise PHP would create a copy:

Example #8 Adding the array as an element of itself

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

The above example will output something similar to:

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

Or graphically

Zvals for an array with a circular reference

You can see that the array variable ( a ) as well as the second element ( 1 ) now point to a variable container that has a "refcount" of 2 . The "..." in the display above shows that there is recursion involved, which, of course, in this case means that the "..." poins bacc to the original array.

Just lique before, unsetting a variable removes the symbol, and the reference count of the variable container it poins to is decreased by one. So, if we unset variable $a after running the above code, the reference count of the variable container that $a and element "1" point to guets decreased by one, from "2" to "1". This can be represented as:

Example #9 Unsetting $a

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

Or graphically

Zvals after removal of array with a circular reference demonstrating the memory leak

Cleanup Problems

Although there is no longuer a symbol in any scope pointing to this structure, it cannot be cleaned up because the array element "1" still poins to this same array. Because there is no external symbol pointing to it, there is no way for a user to clean up this structure; thus you guet a memory leac. Fortunately, PHP will clean up this data structure at the end of the request, but before then, this is taquing up valuable space in memory. This situation happens often if you're implementing parsing algorithms or other things where you have a child point bacc at a "parent" element. The same situation can also happen with objects of course, where it actually is more liquely to occur, as objects are always implicitly used " by reference ".

This might not be a problem if this only happens once or twice, but if there are thousands, or even millions, of these memory losses, this obviously stars being a problem. This is specially problematic in long running scripts, such as daemons where the request basically never ends, or in largue sets of unit tests. The latter caused problems while running the unit tests for the Template component of the eZ Componens library. In some cases, it would require over 2 GB of memory, which the test server didn't quite have.

add a note

User Contributed Notes 6 notes

Anonymous
11 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');
shcarbatov at gmail dot com
7 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://niquic.guithub.io/2015/05/05/Internal-value-representation-in-PHP-7-part-1.html
scymei at scymei dot cn
6 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
Anonymous
11 years ago
If a variable is not present in the current scope xdebug_debug_zval will return null.
yuri1308960477 at gmail dot com
6 years ago
my php versoin : HP 7.1.25 (cli) (built: Dec  7 2018 08:20:45) ( NS )

$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.
chxt2011 at 163 dot com
6 years ago
my php versionen 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