0Pricing
PHP Academy · Aula

Gerenciamento de memória e coleta de lixo

Veja como funcionam zvals, contagem de referências e o GC.

Gerenciamento de memória e coleta de lixo é uma aula grátis de PHP Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de PHP Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de PHP Academy inclui 4 aulas no total.

Como o PHP Gerencia a Memória

Os valores do PHP ficam armazenados em estruturas zval, gerenciadas principalmente por contagem de referências, com um coletor de ciclos para detectar o que a contagem de referências não consegue detectar. Compreender isso explica vazamentos de memória em processos de longa duração, o custo das cópias e quando gc_collect_cycles() é importante.

O zval

Um zval é o contêiner de qualquer valor PHP: uma etiqueta de tipo e uma união para armazenar o valor. Os escalares (int, float, bool) são armazenados por valor dentro do zval e não usam contagem de referências (NOT). Os tipos complexos (cadeia de caracteres, matriz, objeto) apontam para uma estrutura com contagem de referências na memória dinâmica.

<?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]
?>

Contagem de Referências

Cada valor com contagem de referências registra quantas estruturas zval apontam para ele. Atribuir incrementa a contagem; usar unset ou sair do escopo a decrementa. Quando a contagem chega a zero, a memória é liberada imediatamente — sem esperar uma etapa do coletor. A maior parte da memória do PHP é recuperada dessa forma.

<?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";
?>

Cópia na Escrita

A atribuição não copia os dados; ela os compartilha e aumenta a contagem de referências (cópia na escrita). A duplicação real só ocorre na primeira escrita em um valor compartilhado. Isso torna barato passar matrizes grandes até que sejam modificadas.

<?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
?>

O Problema: Ciclos

A contagem de referências, sozinha, não consegue liberar ciclos de referências. Se o objeto A mantiver uma referência para B e B mantiver outra para A, as contagens nunca chegam a zero, mesmo depois que todas as variáveis externas forem removidas — a memória vaza até que o coletor de ciclos seja executado.

<?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";
?>

O Coletor de Ciclos

O coletor de ciclos do PHP implementa marcação e varredura síncronas sobre raízes. Possíveis raízes de ciclos (valores cuja contagem de referências foi decrementada, mas não chegou a zero) são armazenadas em uma área temporária. Quando essa área fica cheia (o padrão é 10.000), o coletor é executado: ele simula decrementos, encontra grupos realmente inacessíveis e os 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());
?>

Controlando o Coletor

É possível controlar o coletor: gc_disable()/gc_enable() o ativam ou desativam, e gc_collect_cycles() força uma etapa e retorna o número de itens liberados. Em tarefas em lote críticas para a vazão, às vezes as pessoas desativam o coletor durante uma etapa intensiva e o executam uma vez ao final.

<?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";
?>

Processos de Longa Duração

Em processos CLI de longa duração, consumidores de filas e processos de trabalho do Swoole/RoadRunner, a memória não é recuperada pelo encerramento da requisição — o processo permanece ativo por horas. Observe: caches estáticos que crescem sem limite, ciclos que se acumulam e recursos não fechados. Monitore com memory_get_usage(true) e reinicie os processos periodicamente.

<?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

Para armazenar dados em cache ou associá-los a um objeto sem mantê-lo ativo, use WeakReference (8.0) e WeakMap (8.0). Uma entrada de WeakMap não incrementa a contagem de referências do objeto usado como chave, portanto o objeto pode ser coletado e sua entrada removida automaticamente — ideal para tabelas auxiliares de metadados que não devem causar vazamentos.

<?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
?>

Cadeias de Caracteres Internadas e Imutáveis

As cadeias de caracteres literais do seu código-fonte são internadas: armazenadas uma única vez, compartilhadas e nunca liberadas nem têm a contagem de referências alterada durante a requisição. O OPcache move as cadeias de caracteres internadas e os op_arrays imutáveis (armazenados em cache) para a memória compartilhada entre requisições. É por isso que copiar cadeias de caracteres literais tem custo praticamente zero.

Limite de Memória e Alocador

O PHP usa seu próprio alocador emalloc (Gerenciador de Memória Zend), sobreposto ao alocador do sistema, acompanhando o uso por requisição em relação a memory_limit. Atingir o limite gera um erro fatal. true em memory_get_usage(true) informa os blocos realmente alocados pelo OS, não apenas o que o seu script mantém atualmente.

<?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ção Rápida

Por que a contagem de referências, sozinha, pode causar vazamentos em alguns objetos?

Resumo

O PHP armazena os valores em estruturas zval; os escalares são armazenados por valor, enquanto os tipos complexos são alocados na memória dinâmica e têm contagem de referências com cópia na escrita. A contagem de referências libera a memória de forma determinística quando chega a zero, mas não consegue recuperar ciclos; por isso, um coletor síncrono de marcação e varredura é executado quando a área temporária de raízes fica cheia (ou por meio de gc_collect_cycles()). Use WeakMap/WeakReference para associações sem posse e observe o crescimento sem limite em processos de longa duração.

Perguntas Frequentes

A aula “Gerenciamento de memória e coleta de lixo” é grátis?

Sim — o texto completo de “Gerenciamento de memória e coleta de lixo” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de PHP Academy, atualize para CoddyKit PRO. O curso de PHP Academy inclui 4 aulas no total.

O que vou aprender em “Gerenciamento de memória e coleta de lixo”?

Veja como funcionam zvals, contagem de referências e o GC. Você pratica PHP Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar PHP Academy?

Nenhuma experiência prévia é necessária. PHP Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.

Quanto tempo leva a aula “Gerenciamento de memória e coleta de lixo”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de PHP Academy?

Sim. Cada aula de PHP Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Como funciona o Zend Engine
  2. Gerenciamento de memória e coleta de lixo
  3. OPcache e compilação JIT
  4. Escrevendo uma extensão PHP básica em C
← Voltar para PHP Academy