Por que nos preocupamos com memória no PHP?
A maioria dos desenvolvedores PHP está acostumada a tratar a memória como um recurso infinito. Criou um array com um milhão de elementos? Sem problemas. Instanciou um objeto e depois esqueceu dele? Tudo bem, o PHP limpa sozinho. Mas quando seu projeto começa a cair com o erro Allowed memory size exhausted ou fica lento do nada, é hora de entender o que realmente acontece nos bastidores.
Hoje vamos mergulhar na caixa-preta chamada "Memória e Garbage Collector no PHP". Você vai descobrir como o PHP conta referências, por que referências circulares são um problema e como fazer o garbage collector trabalhar a seu favor. Vamos nessa!
Como o PHP gerencia a memória: da requisição à resposta
Ao contrário de C ou C++, onde você mesmo aloca e libera memória, o PHP opera através do Zend Engine. Cada processo PHP tem seu próprio pool de memória. Quando você escreve:
$data = [1, 2, 3];
O PHP aloca memória para o array, armazena os valores e incrementa o chamado refcount (contador de referências). Enquanto alguém referenciar a variável $data, a memória não é liberada.
Mas assim que você faz:
unset($data);
o contador de referências cai para zero, e o Zend Engine devolve a memória ao pool instantaneamente. Simples e rápido. Porém, existem nuances…
Aritmética de referências: refcount e is_ref
Cada variável no PHP é uma estrutura zval, que armazena o valor, o tipo, a flag is_ref (se é uma referência) e o refcount. Veja um exemplo:
$a = "Olá";
$b = $a; // refcount aumenta para 2 (cópia na escrita)
$b = "Mundo"; // agora $a e $b são zvals diferentes, refcount de "Olá" cai para 1
Isso é chamado de copy-on-write (cópia na escrita). Enquanto você não altera o valor, o PHP economiza memória usando a mesma área. Mas ao modificar a variável, uma cópia é criada. Esperto, não?
Garbage Collector (GC): quando o refcount não salva
O mecanismo padrão de contagem de referências funciona perfeitamente em 99% dos casos. Mas há um problema: as referências circulares. Imagine que você tem dois objetos que se referenciam mutuamente:
class Node {
public $next;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a;
unset($a);
unset($b);
Após o unset, ambos os objetos ainda têm refcount = 1 (cada um referencia o outro). Formalmente, eles estão inacessíveis pelo código, mas a memória não é liberada. Isso é uma referência circular.
Se houver muitos objetos assim, a memória vai vazar. Para combater isso, a partir do PHP 5.3 foi introduzido um garbage collector (GC) completo.
Como o GC funciona no PHP?
O garbage collector não é executado a cada linha de código. Ele é ativado quando o buffer raiz (um array de possíveis referências circulares) fica cheio. Por padrão, seu tamanho é de 10.000 elementos. Quando o buffer está cheio, o GC executa um algoritmo que:
- Escaneia as raízes — encontra todos os zvals com refcount > 0 que podem fazer parte de um ciclo.
- Diminui o refcount de todos os elementos filhos (como se as referências não existissem).
- Verifica — se após a redução o refcount for zero, significa que é "lixo" e pode ser removido.
- Restaura o refcount para aqueles que não são lixo e libera a memória.
Esse processo é chamado de garbage collection. Você pode forçá-lo manualmente:
gc_collect_cycles();
Mas não faça isso por qualquer motivo — é uma operação cara.
Como rastrear vazamentos de memória no PHP?
Se seu script roda por muito tempo (como um worker de fila ou um daemon), os vazamentos se tornam críticos. Aqui estão algumas ferramentas