How PHP Manages Memory and Why the Garbage Collector Never Sleeps

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Do We Even Think About Memory in PHP?

\n

Most PHP developers are used to thinking of memory as an infinite resource. Requested an array with a million elements? No problem. Created an object and then forgot about it? That's fine, PHP will clean it up itself. But when your project starts crashing with the error Allowed memory size exhausted or unexpectedly slows down for no apparent reason, it's time to understand what's really happening under the hood.

\n

Today we'll look inside the black box called "Memory and Garbage Collector in PHP". You'll learn how PHP counts references, why cyclic references are evil, and how to make the garbage collector work for you, not against you. Let's go!

\n\n

How PHP Manages Memory: From Request to Response

\n

Unlike C or C++, where you allocate and free memory yourself, PHP works through the Zend Engine. Each PHP process has its own memory pool. When you write:

\n
$data = [1, 2, 3];
\n

PHP allocates memory for the array, stores the values, and increments the so-called refcount (reference counter). As long as someone references the $data variable, the memory is not freed.

\n

But as soon as you do:

\n
unset($data);
\n

the reference counter drops to zero, and the Zend Engine instantly returns the memory back to the pool. Simple and fast. However, there are nuances…

\n\n

Reference Arithmetic: refcount and is_ref

\n

Each variable in PHP is a zval structure that stores the value, type, the is_ref flag (whether it's a reference), and refcount. Let's look at an example:

\n
$a = "Hello";\n$b = $a;  // refcount increases to 2 (copy-on-write)\n$b = "World"; // now $a and $b are different zvals, refcount for "Hello" drops to 1\n
\n

This is called copy-on-write. As long as you don't change the value, PHP saves memory by using the same area. But as soon as you modify a variable, a copy is created. Clever, isn't it?

\n\n

Garbage Collector (GC): When refcount Doesn't Help

\n

The normal reference counting mechanism works perfectly for 99% of cases. But there is one problem — cyclic references. Imagine you have two objects that reference each other:

\n
class Node {\n    public $next;\n}\n\n$a = new Node();\n$b = new Node();\n$a->next = $b;\n$b->next = $a;\n\nunset($a);\nunset($b);\n
\n

After unset, both objects still have refcount = 1 (each references the other). Formally, they are inaccessible from the code, but the memory is not freed. This is a cyclic reference.

\n

If there are many such objects, memory will leak. To combat this, starting from version 5.3, PHP introduced a full-fledged garbage collector (GC).

\n\n

How Does the GC Work in PHP?

\n

The garbage collector does not run on every line of code. It is activated when the root buffer (an array of potential cyclic references) is full. By default, its size is 10,000 elements. When the buffer is full, the GC runs an algorithm that:

\n
    \n
  • Scans the roots — finds all zvals with refcount > 0 that could be part of a cycle.
  • \n
  • Decrements refcount for all child elements (as if the references didn't exist).
  • \n
  • Checks — if after decrementing, the refcount becomes zero, it means it's "garbage" and can be deleted.
  • \n
  • Restores refcount for those that turned out not to be garbage, and frees the memory.
  • \n
\n

This process is called garbage collection. You can force it to run:

\n
gc_collect_cycles();
\n

But don't do this for every little thing — it's an expensive operation.

\n\n

How to Track Memory Leaks in PHP?

\n

If your script runs for a long time (e.g., a queue worker or a daemon), leaks become critical. Here are a few tools

Blogs

Book Recommendations