PHP Garbage Collection
PHP uses an automated memory management system. It primarily relies on Reference Counting for immediate memory reclamation and a Cycle Collector to handle circular references.
1. Reference Counting Basics
In PHP, every variable is stored in a structure called a zval.
- Simple Types (PHP 7+): Types like
null,bool,int, andfloatare stored directly in thezvaland do not use reference counting. - Complex Types:
string,array,object,resource, andreferenceuse a reference counter (refcount).
How it works:
- When a variable is created or assigned to another variable, the
refcountof the underlying data increases. - When a variable goes out of scope or is
unset(), therefcountdecreases. - If the
refcountreaches zero, PHP immediately frees the memory.
$a = ["hello"]; // refcount = 1
$b = $a; // refcount = 2
unset($a); // refcount = 1
unset($b); // refcount = 0 -> Memory freed
2. Collecting Cycles (Circular References)
Reference counting alone cannot handle circular references, where two or more objects point to each other.
$a = new stdClass();
$b = new stdClass();
$a->child = $b;
$b->parent = $a;
unset($a, $b);
In this example, even after unset(), both objects still have a refcount of 1 because they point to each other. This would cause a memory leak if not for the Cycle Collector.
The Cycle Collection Algorithm (Bacon-Rajan)
PHP uses a concurrent cycle collection algorithm that runs when a "root buffer" of potential cycles reaches a certain limit (default: 10,000).
- Root Buffer: When a
refcountis decreased but remains > 0, PHP suspects a cycle and adds thezvalto the root buffer. - Marking (Gray): The GC performs a depth-first search starting from the roots, decrementing the
refcountof every internalzvalit finds. - Scanning (White/Black):
- If a
zval'srefcountis now 0, it is marked as garbage (white). - If the
refcountis > 0, it is marked as reachable (black) and itsrefcountis restored.
- If a
- Sweeping: All "white"
zvalsare deleted from memory.
Scheme: GC Cycle Collection
graph TD
A[Start GC] --> B[Mark potential roots as Purple]
B --> C[Depth-First Search: Decrement refcounts - Mark Gray]
C --> D{Is refcount == 0?}
D -- Yes --> E[Mark White - Garbage]
D -- No --> F[Increment back - Mark Black]
E --> G[Sweep: Free Memory]
F --> G
G --> H[End GC]
3. Performance Considerations
CPU vs. Memory
- GC Enabled (Default): Prevents memory leaks but introduces periodic CPU overhead when the collection cycle runs.
- GC Disabled: Slightly faster execution (useful for very short-lived scripts) but can lead to high memory usage if circular references exist.
Manual Control
You can control the garbage collector using these functions:
gc_enable()/gc_disable(): Turn the collector on or off.gc_collect_cycles(): Manually trigger a collection cycle.gc_status(): Returns statistics about the current state of the GC.
Optimizations in Modern PHP
- PHP 7.3+: Significantly improved the performance of the cycle collector.
- PHP 8.0+: Better handling of internal structures.
- PHP 8.3+: Added the ability to configure the maximum number of roots via
zend.gc_max_rootsand enhancedgc_status()output (addingrunning,protected,fullflags).
Best Practices
- For long-running processes (daemons, workers), always keep GC enabled.
- Avoid creating deep circular structures if performance is critical.
- Use
gc_mem_caches()(PHP 7+) to free up memory used by the Zend Engine memory manager.