Memory Management — Stack vs Heap
debt(d7/e5/b7/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints list specialist tools — blackfire, php-meminfo, datadog — but these require deliberate instrumentation and profiling setup. Memory issues typically surface as 'memory_limit hit in production' (per code_pattern), meaning the problem is often silent until it manifests under real load. Not caught by compiler or default linters, and requires specialist tooling or production monitoring to detect, placing it between d5 and d9; d7 because production exposure is the primary discovery path even when tools exist.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix mentions several remediation steps: monitoring memory_get_peak_usage(), switching to generators for large datasets, adding unset() calls, and potentially upgrading PHP version. Common mistakes involve circular references, large array accumulation in loops, and long-running script patterns. These fixes are not a single-line swap — refactoring a loop to use generators or auditing long-running scripts for unset() calls touches multiple locations and requires rethinking data-flow patterns, but stops short of full architectural rework.
Closest to 'strong gravitational pull' (b7). The term applies_to web, cli, and queue-worker contexts — the full breadth of PHP usage. Long-running scripts (queue workers, CLI commands) are shaped by this constraint: every large dataset operation, every object accumulation pattern, and every loop must be considered through the lens of memory impact. The misconception that 'PHP's GC handles everything' means teams often build with no memory discipline, and correcting that shapes how generators, unset(), and reference patterns are used across the codebase.
Closest to 'serious trap' (t7). The misconception is explicitly stated: 'PHP developers don't need to understand memory management — PHP's GC handles allocation.' This is a credible but wrong belief that contradicts how PHP's reference-counting GC actually behaves with circular references. A competent developer from a managed-memory language (Java, Python, C#) would reasonably assume the GC handles cycles transparently, but PHP's refcount mechanism requires explicit intervention (unset(), gc_collect_cycles()) for cyclic structures. The trap is particularly dangerous in long-running processes where the 'obvious' GC-trusting approach silently accumulates leaks.
Also Known As
TL;DR
Explanation
Stack memory: allocated at function call, freed at return. Stores: local variables, function parameters, return addresses. Fixed size (typically 8MB on Linux). Extremely fast — just a pointer increment. Stack overflow: too many nested calls exhaust the stack. Heap memory: allocated with new/malloc, freed by garbage collection or explicit free. Stores: objects, arrays, large data structures. PHP uses a reference-counting GC with cycle collector. ZendMM (Zend Memory Manager) manages PHP's heap. PHP does not expose stack vs heap to the developer — all variables are reference-counted heap values by default.
Common Misconception
Why It Matters
Common Mistakes
- Circular references without unset() — PHP's refcount GC handles most cycles but cyclic destruction order can cause leaks.
- Appending to a huge array in a loop instead of using generators.
- Not calling unset() on large variables in long-running scripts.
- Using PHP's GC with long-lived objects — gc_collect_cycles() can be called manually for cycle collection.
Code Examples
// Memory leak via circular reference:
class Node {
public ?Node $parent = null;
public array $children = [];
}
$parent = new Node();
$child = new Node();
$parent->children[] = $child;
$child->parent = $parent; // Circular: parent→child→parent
unset($parent, $child); // Refcount never reaches 0 — leak!
// PHP cycle GC eventually collects these, but slowly
// Break circular references explicitly:
unset($child->parent); // Break cycle before unset
unset($parent, $child); // Now refcount reaches 0 immediately
// Or use WeakReference for back-references:
class Node {
public ?\WeakReference $parent = null; // Does not increment refcount
}
$child->parent = \WeakReference::create($parent);
// When $parent is unset, WeakReference returns null — no cycle