Memory management is one of the most critical aspects of any programming language. In PHP, a language designed to handle dynamic and complex web applications, memory is allocated and freed automatically. This automatic process is powered by Garbage Collection (GC).

Garbage Collection (GC) is the process by which PHP identifies and frees up memory occupied by variables, objects, or arrays that are no longer in use.

PHP automatically manages memory through reference counting and cyclic garbage collection. This ensures that scripts do not consume more memory than necessary, avoiding memory leaks and performance degradation.


Reference Counting

At its core, PHP uses reference counting to track how many variables reference a particular piece of memory (zval container).

  • Each variable or object is stored in memory and has a reference counter.
  • When a new variable references the same value, the counter is incremented.
  • When a reference is destroyed (unset or goes out of scope), the counter is decremented.
  • When the counter reaches zero, PHP immediately frees that memory.

- Example:

$a = "Hello";   // Reference counter = 1
$b = $a;        // Reference counter = 2
unset($a);      // Reference counter = 1
unset($b);      // Reference counter = 0 => Memory freed

This mechanism works well in most cases, but it fails with circular references.


The Problem of Circular References

A circular reference occurs when two or more objects reference each other, preventing their reference counters from ever reaching zero.

- Example:

class Node
{
    public $child;
}

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

$a->child = $b;
$b->child = $a;

// Both $a and $b reference each other => reference count never reaches 0

In such cases, PHP's reference counting alone cannot clean up the memory. This leads to memory leaks.

To solve this, PHP introduced Cyclic Garbage Collection starting with PHP 5.3.


Cyclic Garbage Collection

The cyclic garbage collector (GC) is responsible for detecting and cleaning up circular references.

  • Runs periodically, not continuously.
  • Works specifically on zvals that are part of object graphs (arrays, objects).

- How It Works:

  • PHP maintains a buffer of possible roots (zvals that could participate in cycles).
  • When the buffer is full or GC is triggered manually, PHP analyzes these roots.
  • It uses algorithms to detect cycles (objects referencing each other).
  • If cycles are found and confirmed unreachable, PHP reclaims their memory.

Garbage Collection Process

The process can be broken into three phases:

  1. Root Buffering - Potential cyclic structures are added to a buffer.
  2. Marking - PHP marks references to detect cycles.
  3. Sweep - If cycles are found with no external references, memory is released.

Controlling Garbage Collection

PHP provides several functions to control the GC:

- Enable/Disable GC

gc_enable();   // Enable GC
gc_disable();  // Disable GC

- Check GC Status

if (gc_enabled()) {
    echo "Garbage Collection is enabled";
}

- Trigger GC Manually

gc_collect_cycles();
// Force collection of all existing garbage cycles

This is useful in long-running scripts (e.g., daemons, CLI tools, or large imports).


Configuration Directives for Garbage Collection

In php.ini, several directives influence memory management:

  • zend.enable_gc - Enables/disables garbage collection globally (default: On).
  • gc_divisor - Defines how frequently GC runs.
  • gc_probability - Together with gc_divisor, determines the probability that GC will start during script execution.

- Example:

zend.enable_gc = 1
gc_probability = 1
gc_divisor = 1000

This means that on average, 1 in 1000 allocations will trigger GC.


Performance Considerations

While GC prevents memory leaks, it comes with overhead.

- Best Practices:

  • Avoid unnecessary circular references - Break references when no longer needed.
  • Use unset() strategically - Helps decrease reference counts earlier.
  • Manually trigger GC in long-running processes - Especially in CLI scripts.
  • Profile memory usage - Tools like memory_get_usage() and memory_get_peak_usage() are invaluable.

Garbage Collection and PHP Versions

  • PHP 4.x - 5.2: Only reference counting, no GC => memory leaks in circular references.
  • PHP 5.3: Introduction of cyclic garbage collection.
  • PHP 7.x: Improved memory usage, faster GC.
  • PHP 8.x: More efficient algorithms, lower overhead, better handling of references.

Garbage Collection in Real-World Applications

- Example: Long-running Worker

while (true) {
    $data = getDataFromQueue();
    process($data);
    
    unset($data);
    gc_collect_cycles(); // prevent leaks in loops
}

- Example: Object Graphs

When dealing with ORM frameworks (e.g., Doctrine, Eloquent), objects often reference each other. Without GC, large applications would leak memory quickly.


Monitoring Garbage Collection

You can monitor GC stats using:

gc_collect_cycles();
print_r(gc_status());

- Sample output:

Array
(
    [runs] => 3
    [collected] => 25
    [roots] => 0
)
  • runs: Number of GC runs.
  • collected: Number of collected cycles.
  • roots: Current possible root elements in buffer.

Source: Orkhan Alishov's notes