← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Garbage Collection

Compiler PHP 5.3+ Advanced
debt(d7/e3/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints list Blackfire and php-meminfo as tools — these are specialist profilers that require active instrumentation of the running process. Memory growth from circular references in a queue worker is silent in development and only visible when monitoring memory over time in production-like conditions. It doesn't surface as a compiler or linter warning, and default static analysis won't flag missing gc_collect_cycles() calls.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix cites calling gc_collect_cycles() manually in long-running scripts, and common_mistakes point to unsetting large arrays/objects and avoiding circular references in listeners. These fixes are small but require identifying all affected loops and batch-processing points — slightly more than a single one-line patch but confined within one or a few scripts/components.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). The applies_to scope is cli and queue-worker contexts, not all PHP, so it doesn't reach the entire codebase. However, in any long-running PHP process the concern is pervasive — every loop, every object graph, every event listener registration must be written with GC awareness, imposing an ongoing mental tax on maintainers working in those contexts.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception field states directly: developers believe PHP's garbage collector handles all memory automatically, but circular references are only freed by the cycle collector which runs periodically — not immediately on unset. This contradicts the intuitive model that 'when I'm done with an object, memory is freed,' which is how reference counting works for non-circular structures. This mismatch causes real memory exhaustion bugs in production queue workers.

About DEBT scoring →

Also Known As

reference counting cycle collector GC memory management

TL;DR

Automatic memory management that reclaims objects no longer reachable by the program — PHP uses reference counting with a cycle collector for circular references.

Explanation

PHP's primary GC is reference counting: each value tracks how many variables point to it; when the count reaches 0, the value is freed immediately. Problem: circular references (A → B → A) never reach 0. PHP's cycle collector (since 5.3) detects and frees circular reference groups. In PHP 8, the GC is more efficient. Long-running processes (queue workers, PHP-FPM long-lived workers) accumulate memory through leaks or slow GC; calling gc_collect_cycles() manually in batch jobs can reduce memory usage.

Diagram

flowchart TD
    subgraph Reference Counting
        OBJ[Object<br/>refcount=1] -->|assign to 2nd var| RC2[refcount=2]
        RC2 -->|unset 1st var| RC1[refcount=1]
        RC1 -->|unset all| RC0[refcount=0<br/>free immediately]
    end
    subgraph Cycle Collector
        A[Object A<br/>points to B] -->|circular ref| B[Object B<br/>points to A]
        B --> A
        RC0B[Neither reaches 0<br/>both stranded]
        CYCLE[Cycle Collector<br/>runs periodically] -->|detects cycle| FREE[Frees both]
    end
    style RC0 fill:#238636,color:#fff
    style FREE fill:#238636,color:#fff

Watch Out

PHP's reference counter frees most objects immediately on unset, but circular references (parent→child→parent) are only reclaimed by the cycle collector — which runs periodically, not instantly. A long-running worker that builds trees without breaking cycles will leak memory steadily.

Common Misconception

PHP's garbage collector handles all memory automatically — circular references are only freed by the cycle collector, which runs periodically; large circular reference structures can accumulate in long-running scripts.

Why It Matters

PHP queue workers and long-running scripts that create circular references or large object graphs can slowly exhaust memory — understanding GC enables writing memory-efficient persistent PHP processes.

Common Mistakes

  • Not unsetting large arrays and objects after use in loops — reference count stays > 0 until scope ends.
  • Circular references in event listeners — object A registers a listener on object B; both hold references to each other.
  • Not calling gc_collect_cycles() in very long batch jobs — cycle collector runs automatically but not immediately.
  • PHP generators as memory optimisation — they yield one item at a time, but the generator object itself stays in memory.

Avoid When

  • Do not disable gc_disable() in long-running workers without a manual gc_collect_cycles() strategy — circular references accumulate silently until OOM.
  • Avoid creating large object graphs with circular parent/child references when a simple array or flat structure would suffice.

When To Use

  • Call gc_collect_cycles() explicitly after processing large batches of objects in long-running scripts to reclaim memory from circular references immediately.
  • Use WeakReference for cache or observer registrations where the GC should be free to collect the target without the reference preventing it.

Code Examples

💡 Note
The bad example builds a parent/child tree with circular references that the reference counter cannot free — memory grows per iteration. The fix nulls the parent reference before unsetting, breaking the cycle so memory is reclaimed immediately.
✗ Vulnerable
// Circular reference — not immediately freed:
class Node {
    public ?Node $parent = null;
    public array $children = [];
}
$root = new Node();
$child = new Node();
$root->children[] = $child;
$child->parent = $root; // Circular: root → child → root
unset($root, $child); // Reference count > 0 — not freed immediately
// Freed later by cycle collector — or manually: gc_collect_cycles()
✓ Fixed
// Break circular references explicitly:
function processTree(Node $root): void {
    foreach ($root->children as $child) processTree($child);
    // Break circular refs before returning:
    foreach ($root->children as $child) $child->parent = null;
    $root->children = [];
}
// Or use WeakReference for parent pointers — does not prevent GC
class Node {
    public ?WeakReference $parent = null; // Weak — GC can collect parent
}

Added 15 Mar 2026
Edited 28 Apr 2026
Views 95
AI edit PF Media Bot Claude Opus 4.5 on refs · 25 Apr 2026
Edits history 1 edit
  1. refs PF Media Bot Claude Opus 4.5 · 25 Apr 2026
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 1 ping W 0 pings T 3 pings F 1 ping S 3 pings S 0 pings M 2 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 3 pings M 0 pings T 0 pings W 0 pings T 0 pings F 2 pings S 0 pings S 1 ping M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Amazonbot 14 Scrapy 9 Perplexity 7 SEMrush 5 Google 4 Ahrefs 4 Unknown AI 3 Majestic 2 Claude 2 Meta AI 1 PetalBot 1
crawler 49 crawler_json 2 pre-tracking 1
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
PHP uses reference counting with a cycle collector — call gc_collect_cycles() manually in long-running CLI scripts processing many objects to reclaim circular reference memory
📦 Applies To
PHP 5.3+ cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
Memory growing in queue worker over time; circular object references not freed; gc_collect_cycles() never called in long CLI batch
Auto-detectable: ✓ Yes blackfire php-meminfo
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: Medium Context: File


✓ schema.org compliant