PHP Memory Management and Garbage Collection

This article provides a comprehensive guide on how PHP manages memory and handles garbage collection. It covers the core mechanics of reference counting, the challenges of circular references, how PHP’s cycle-detecting engine solves these issues, and how developers can configure garbage collection to optimize application performance.

Reference Counting in PHP

At the core of PHP’s memory management is “reference counting.” Every PHP variable is stored in a container structure called a zval (Zend Value). In addition to the variable’s type and value, a zval contains two extra pieces of metadata:

When you assign a variable to another, PHP does not immediately duplicate the value in memory. Instead, it points both variables to the same zval and increments the refcount.

$a = "hello"; // zval created: refcount = 1, is_ref = false
$b = $a;      // same zval used: refcount = 2, is_ref = false

When a variable goes out of scope, is overwritten, or is explicitly destroyed using unset(), the refcount of its corresponding zval is decremented by one. If the refcount reaches zero, PHP immediately frees that memory back to the system.

The Challenge of Circular References

While reference counting is fast and efficient, it fails to handle circular references. A circular reference occurs when two or more objects reference each other, or when an array/object references itself.

$a = new stdClass();
$b = new stdClass();

$a->self = $b;
$b->self = $a;

unset($a);
unset($b);

In this scenario, after calling unset(), the user-space variables $a and $b are gone. However, because the objects still point to each other, their refcount values only drop from 2 to 1. Since the refcount is not zero, standard reference counting cannot free this memory. This leads to a memory leak.

The Cycle Collecting Algorithm

To solve circular references, PHP uses a cyclic garbage collection algorithm. Instead of constantly scanning all memory, the garbage collector only watches potential “roots.” A root is any zval (specifically arrays and objects) whose refcount decreases but does not reach zero.

When a root is detected, PHP places it into a root buffer, which has a default capacity of 10,000 nodes. Once the buffer is full, the garbage collection process is triggered:

  1. Simulation: The algorithm traverses all the nodes in the buffer and decrements their internal reference counts by one, tracking the changes.
  2. Analysis: The algorithm checks which zvals now have a simulated refcount of zero. If a zval’s count drops to zero, it means the variable was kept alive solely by a circular reference.
  3. Restoration: The algorithm reverses the simulated decrement for any zval whose simulated count did not reach zero, restoring its original reference count.
  4. Reclamation: The zvals confirmed to be garbage (those with a simulated count of zero) are freed from memory.

Managing Garbage Collection Manually

By default, PHP has garbage collection enabled. However, developers can control and monitor this system using several built-in functions: