0Pricing
PHP Academy · Lezione

Gestione della memoria e garbage collection

Scopra come funzionano zval, conteggio dei riferimenti e GC

Gestione della memoria e garbage collection è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.

Come PHP gestisce la memoria

I valori PHP risiedono negli zval, gestiti principalmente tramite il conteggio dei riferimenti, con un garbage collector dei cicli per i casi che il conteggio dei riferimenti non può gestire. Comprendere questo meccanismo aiuta a spiegare le perdite di memoria nei worker a esecuzione prolungata, il costo delle copie e quando sia importante usare gc_collect_cycles().

Lo zval

Uno zval è il contenitore di qualsiasi valore PHP: include un tag di tipo e una union che contiene il valore. Gli scalari (int, float, bool) sono memorizzati per valore nello zval e NON sono soggetti al conteggio dei riferimenti. I tipi complessi (stringa, array, oggetto) puntano a una struttura soggetta al conteggio dei riferimenti nell'heap.

<?php
// debug_zval_refcount-style inspection via gc + count
$arr = [1, 2, 3];
$copy = $arr;   // refcount of the array bumps, no data copy yet (CoW)

// Modifying triggers copy-on-write separation
$copy[] = 4;

var_dump($arr);   // still [1,2,3]
var_dump($copy);  // [1,2,3,4]
?>

Conteggio dei riferimenti

Ogni valore soggetto al conteggio dei riferimenti tiene traccia del numero di zval che lo referenziano. L'assegnazione incrementa il conteggio; la rimozione o l'uscita dallo scope lo decrementano. Quando il conteggio raggiunge zero, la memoria viene liberata immediatamente: non occorre attendere un passaggio del GC. La maggior parte della memoria PHP viene recuperata in questo modo.

<?php
$a = str_repeat('x', 1000);  // refcount 1 on the string
$b = $a;                      // refcount 2
unset($a);                    // refcount 1
unset($b);                    // refcount 0 -> freed instantly

echo "Refcounting frees deterministically\n";
?>

Copy-on-Write

L'assegnazione non copia i dati: li condivide e incrementa il conteggio dei riferimenti (copy-on-write). La duplicazione effettiva avviene solo alla prima scrittura su un valore condiviso. Per questo passare array di grandi dimensioni è poco costoso finché non vengono modificati.

<?php
$big = range(1, 100000);
$shared = $big;          // O(1): shared, refcount++

// Reads stay shared and cheap
echo count($shared), PHP_EOL;

// First write separates (copies) — now it costs O(n)
$shared[0] = -1;
echo $big[0], ' vs ', $shared[0], PHP_EOL;  // 1 vs -1
?>

Il problema: i cicli

Il solo conteggio dei riferimenti non può liberare i cicli di riferimenti. Se l'oggetto A contiene un riferimento a B e B ne contiene uno ad A, i rispettivi conteggi non raggiungono mai zero, anche dopo aver rimosso ogni variabile esterna: la memoria perde efficacemente il riferimento finché non viene eseguito il garbage collector dei cicli.

<?php
class Node { public ?Node $ref = null; }

$a = new Node();
$b = new Node();
$a->ref = $b;   // a -> b
$b->ref = $a;   // b -> a  (cycle!)

unset($a, $b);  // external refs gone, but internal cycle keeps refcount > 0

// The objects are unreachable but NOT yet freed by refcounting alone.
echo gc_collect_cycles() . " cycles collected\n";
?>

Il garbage collector dei cicli

Il GC di PHP implementa un mark-and-sweep sincrono sulle radici. Le potenziali radici di cicli, cioè i valori il cui conteggio dei riferimenti è stato decrementato senza raggiungere zero, vengono inserite in un buffer. Quando il buffer delle radici si riempie, per impostazione predefinita a 10,000 elementi, il GC entra in funzione: simula i decrementi, individua i gruppi realmente irraggiungibili e li libera.

<?php
// GC status exposes the root buffer and run stats
for ($i = 0; $i < 3; $i++) {
    $a = new stdClass();
    $b = new stdClass();
    $a->b = $b; $b->a = $a;   // create a cycle
    unset($a, $b);            // becomes a GC root
}

print_r(gc_status());
?>

Controllare il GC

Può controllare il garbage collector: gc_disable()/gc_enable() lo disattivano e lo riattivano, mentre gc_collect_cycles() forza un passaggio e restituisce il numero di elementi liberati. Nei lavori batch in cui il throughput è fondamentale, a volte si disattiva il GC durante una fase intensa e si esegue una sola raccolta al termine.

<?php
gc_disable();              // no automatic cycle collection

// ... heavy phase that creates and drops many cycles ...
for ($i = 0; $i < 1000; $i++) {
    $x = new stdClass(); $y = new stdClass();
    $x->y = $y; $y->x = $x; unset($x, $y);
}

$freed = gc_collect_cycles();   // reclaim all at once
gc_enable();
echo "Freed $freed objects\n";
?>

Worker a esecuzione prolungata

Nei demoni CLI, nei consumer di code e nei worker Swoole/RoadRunner, la memoria non viene recuperata alla chiusura della richiesta: il processo rimane in esecuzione per ore. Presti attenzione a cache statiche che crescono senza limiti, cicli che si accumulano e risorse non chiuse. Monitori l'utilizzo con memory_get_usage(true) e riavvii periodicamente i worker.

<?php
$peakStart = memory_get_usage(true);

// Simulate processing a batch of jobs
for ($job = 0; $job < 5; $job++) {
    $payload = range(0, 10000);
    // ... process ...
    unset($payload);
}

printf("start: %d KB, peak: %d KB, now: %d KB\n",
    $peakStart >> 10,
    memory_get_peak_usage(true) >> 10,
    memory_get_usage(true) >> 10
);
?>

WeakReference e WeakMap

Per memorizzare dati nella cache o associarli a un oggetto senza mantenerlo in vita, utilizzi WeakReference (8.0) e WeakMap (8.0). Una voce di WeakMap non incrementa il conteggio dei riferimenti dell'oggetto chiave, quindi l'oggetto può essere raccolto e la relativa voce viene rimossa automaticamente: è l'ideale per tabelle laterali di metadati che non devono causare perdite di memoria.

<?php
$map = new WeakMap();

$obj = new stdClass();
$map[$obj] = 'metadata';      // does NOT keep $obj alive

echo count($map) . PHP_EOL;   // 1
unset($obj);                  // object collectible -> entry removed
echo count($map) . PHP_EOL;   // 0
?>

Stringhe internate e immutabili

Le stringhe letterali nel codice sorgente sono internate: vengono memorizzate una sola volta, condivise e non sono mai soggette al conteggio dei riferimenti né liberate durante la richiesta. OPcache sposta le stringhe internate e gli op_array immutabili, memorizzati nella cache, nella memoria condivisa tra le richieste. Per questo copiare stringhe letterali è di fatto gratuito.

Limite di memoria e allocatore

PHP utilizza il proprio allocatore emalloc, lo Zend Memory Manager, costruito sopra l'allocatore di sistema, e tiene traccia dell'utilizzo per richiesta rispetto a memory_limit. Il superamento del limite genera un errore irreversibile. true in memory_get_usage(true) restituisce i blocchi realmente allocati dal sistema operativo, non solo ciò che lo script contiene attualmente.

<?php
echo 'limit: ' . ini_get('memory_limit') . PHP_EOL;
echo 'emalloc usage: ' . memory_get_usage() . " bytes\n";   // ZendMM tracked
echo 'real usage:    ' . memory_get_usage(true) . " bytes\n"; // from OS
?>

Verifica rapida

Perché il solo conteggio dei riferimenti può causare perdite di memoria per alcuni oggetti?

Riepilogo

PHP memorizza i valori negli zval; gli scalari sono gestiti per valore, mentre i tipi complessi vengono allocati nell'heap e sono soggetti al conteggio dei riferimenti, con copy-on-write. Il conteggio dei riferimenti libera la memoria in modo deterministico quando arriva a zero, ma non può recuperare i cicli; per questo viene eseguito un garbage collector sincrono mark-and-sweep quando il buffer delle radici si riempie, oppure tramite gc_collect_cycles(). Utilizzi WeakMap/WeakReference per associazioni non proprietarie e presti attenzione alla crescita senza limiti nei worker a esecuzione prolungata.

Domande Frequenti

La lezione «Gestione della memoria e garbage collection» è gratuita?

Sì — il testo completo di «Gestione della memoria e garbage collection» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.

Cosa imparerò in «Gestione della memoria e garbage collection»?

Scopra come funzionano zval, conteggio dei riferimenti e GC Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare PHP Academy?

Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Gestione della memoria e garbage collection»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione PHP Academy?

Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Come funziona Zend Engine
  2. Gestione della memoria e garbage collection
  3. OPcache e compilazione JIT
  4. Scrivere un'estensione PHP di base in C
← Torna a PHP Academy