As noted in the garbage collection docs, which are terribly out of date and hard to trust:
The whole reason for implementing the garbage collection mechanism is to reduce memory usage by cleaning up circular-referenced variables as soon as the prerequisites are fulfilled. In PHP's implementation, this happens as soon as the root-buffer is full, or when the function gc_collect_cycles is called.
Even if your code has no memory leaks, you can still run out of memory. It's ultimately a question of what happens first:
- GC is enabled (default) and the
threshold*
of root objects is met,
or gc_collect_cycles
is called. PHP will collect unreferenced
objects and free up memory. Your script will happily run along.
- Your script runs out of memory.
[*] I am uncertain as to how the threshold
is determined -- it appears to be magically set and can change as your script executes. You can call gc_status() to see the current threshold, and how many roots there are.
If you are encountering out of memory problems because memory runs out before the threshold is met, I do not know how to fix this besides manually calling gc_collect_cycles()
. Hopefully somebody can shed some light on this!
Now, your questions:
Is only "a" in the root buffer or does $a[0] end up in there too?
From the reference counting basics docs (search for Compound Types
), only $a
will be added to the root buffer. This is because it is a compound type (array, or object). In the diagram in the docs, only what is within the dotted line is in the root buffer
-- in this case, a single reference to the "a" object.
See below:
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
)

What happens if you end up with more than 10K roots? Does it stop collecting at some point?
As soon as it reaches the threshold, PHP should do garbage collection.
Although these docs do mention the 10K
roots limit, I do not believe that is accurate for PHP 7.3+. As noted before, you can run gc_status()
to view the current threshold at any time. Even the docs for gc_status show an example threshold of 50000, and not 10000.
I was able to find a reddit post that confirms PHP runs heuristics to determine the threshold. It is no longer fixed at 10000.
There is also a bug report that suggests changing the docs.
Finally, should I be concerned about 0% efficiency collections in the xdebug garbage collection report?
From xdebug docs:
Efficiency% — Is the number of cleared roots divided by 10000 - a magic number of "roots" when reached triggers PHPs internal garbage collector to run automatically.
I don't use xdebug myself, so I don't know if it's been changed to divide by the threshold at the current time (If it still uses 10000, it would be possible to get >100% efficiency.)
But, in short, 0% efficiency would mean that all of the objects/arrays you've created are being referenced by something that is ultimate referenced by the root level scope. As far as I know, PHP can resolve circular references and clean them up, so this is very likely a memory leak.