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

PHP Memory Model — Zval & Copy-on-Write

php PHP 7.0+ Advanced
debt(d7/e5/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). Memory issues from misunderstanding CoW don't trigger any compiler or linter warnings. Detection requires profiling with memory_get_peak_usage() or observing unexpected memory spikes in production. No detection_hints.tools specified for this term, and standard PHP linters don't catch CoW-related misconceptions.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests profiling to identify the problem, but fixing CoW-related memory issues often requires understanding data flow through multiple functions. Changing how arrays are passed or restructuring code to avoid unintended copies can span several files, though it rarely requires architectural changes.

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

Closest to 'persistent productivity tax' (b5). CoW behavior applies across all PHP contexts (web, cli) and affects any code working with arrays and objects. It's a fundamental aspect of PHP internals that shapes how developers should think about memory, but it doesn't define system architecture — it's more of an ongoing consideration when writing performant code with large data structures.

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

Closest to 'serious trap' (t7). The misconception states developers believe 'PHP copies arrays on assignment like a value type' when it actually uses CoW. This contradicts intuition from other languages and leads to unnecessary reference passing (&$arr) or confusion about when memory is actually freed. The object-reference vs array-CoW distinction adds another layer of confusion that trips up experienced developers.

About DEBT scoring →

Also Known As

zval copy-on-write PHP CoW PHP reference counting PHP internals memory

TL;DR

PHP stores every value in a zval (Zend value) container that holds the type, value, and a reference count. Arrays and strings are copied lazily — the copy only happens when one copy is modified (copy-on-write), making passing large values cheap until mutation.

Explanation

In PHP 7+, a zval is a compact C struct holding a type tag and a union of the actual value (long, double, pointer to string/array/object). For scalar values (int, float, bool, null), the zval holds the value directly — no heap allocation. For strings and arrays, the zval holds a pointer to a reference-counted heap structure. When you assign '$b = $a' where $a is an array, PHP does not copy the array — it increments the reference count and both variables point to the same structure. Only when either variable is modified does PHP perform a real copy ('copy-on-write'). Objects behave differently — they are always reference types; assignment copies the handle, not the object.

Common Misconception

PHP copies arrays on assignment like a value type. It does not — assignment increments a reference count. The copy is deferred until one copy is written to. Functions that read but do not modify their array argument add zero copy overhead.

Why It Matters

Understanding CoW explains why passing large arrays to functions is cheap in PHP 7+ (no copy until mutation) but why modifying an array inside a function creates a full copy. It also explains why 'unset($largeArray)' frees memory immediately only if no other variable references it, and why circular references require a separate cycle collector on top of reference counting.

Common Mistakes

  • Assuming unset() immediately frees memory — it decrements the reference count; memory is only freed when the count reaches zero, which won't happen if other variables reference the same data.
  • Passing arrays by reference (&$arr) to avoid copying — in PHP 7+ this is usually unnecessary; CoW makes value passing cheap for read-only operations.
  • Forgetting that objects are always reference types — '$b = $a' for objects copies the handle, not the object; both variables see mutations.
  • Not accounting for circular references — reference counting cannot collect cycles (A → B → A); PHP's cycle collector runs periodically but not immediately.

Code Examples

✗ Vulnerable
<?php
// ❌ Accidentally triggering CoW — sorting inside function copies the array
function getTopItems(array $items, int $n): array
{
    sort($items); // Modifies $items — triggers full CoW copy of the passed array
    return array_slice($items, 0, $n);
}

// ❌ Holding references to large arrays longer than needed
$cache = [];
foreach ($largeDataset as $row) {
    $cache[] = $row; // $largeDataset reference count stays high
}
✓ Fixed
<?php
// ✅ Sort a copy explicitly — makes intent clear, CoW still applies
function getTopItems(array $items, int $n): array
{
    $sorted = $items; // Cheap — increments refcount only
    sort($sorted);    // CoW copy happens here, only $sorted is affected
    return array_slice($sorted, 0, $n);
}

// ✅ Unset large variables when done to reduce refcount
foreach ($largeDataset as $row) {
    process($row);
}
unset($largeDataset); // Drops refcount — memory freed if no other refs

Added 23 Mar 2026
Views 29
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings F 0 pings S 2 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 4 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 2 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F
No pings yet today
No pings yesterday
Amazonbot 10 Perplexity 7 ChatGPT 3 Google 2 Meta AI 1 Ahrefs 1
crawler 23 crawler_json 1
DEV INTEL Tools & Severity
⚡ Quick Fix
Profile memory with memory_get_peak_usage() before and after suspect operations. If a function that 'only reads' an array shows high memory, check whether it modifies the array internally (even temporarily) — that triggers a full CoW copy.
📦 Applies To
PHP 7.0+ web cli

✓ schema.org compliant