0Pricing
PHP Academy · Lección

Gestión de memoria y recolección de basura

Comprenda cómo funcionan los zvals, el recuento de referencias y el GC

Gestión de memoria y recolección de basura es una lección gratuita de PHP Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de PHP Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de PHP Academy incluye 4 lecciones en total.

Cómo administra PHP la memoria

Los valores de PHP viven en zvals, administrados principalmente mediante el conteo de referencias, con un recolector de ciclos para detectar lo que el conteo de referencias no puede liberar. Comprender esto permite explicar las fugas de memoria en procesos de larga duración, el coste de las copias y cuándo es importante gc_collect_cycles().

El zval

Un zval es el contenedor de cualquier valor de PHP: contiene una etiqueta de tipo y una unión para almacenar el valor. Los escalares (int, float, bool) se almacenan por valor dentro del zval y NO cuentan con conteo de referencias. Los tipos complejos (string, array, object) apuntan a una estructura con conteo de referencias en el montón.

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

Conteo de referencias

Cada valor con conteo de referencias registra cuántos zvals apuntan a él. Al asignarlo, el contador aumenta; al anularlo o salir del ámbito, disminuye. Cuando el contador llega a cero, la memoria se libera inmediatamente, sin esperar a una pasada del recolector de basura. La mayor parte de la memoria de PHP se recupera de esta manera.

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

Copia al escribir

La asignación no copia los datos; los comparte y aumenta el conteo de referencias (copia al escribir). La duplicación real solo ocurre en la primera escritura sobre un valor compartido. Esto hace que pasar matrices grandes sea barato hasta que se modifican.

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

El problema: los ciclos

El conteo de referencias por sí solo no puede liberar los ciclos de referencias. Si el objeto A mantiene una referencia al objeto B y B mantiene otra a A, sus contadores nunca llegan a cero, incluso después de anular todas las variables externas; la memoria se filtra hasta que se ejecuta el recolector de ciclos.

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

El recolector de ciclos

El recolector de basura de PHP implementa un marcado y barrido sincrónico sobre las raíces. Las posibles raíces de ciclos (valores cuyo conteo de referencias disminuyó, pero no llegó a cero) se almacenan en un búfer. Cuando el búfer de raíces se llena (10 000 elementos de forma predeterminada), se ejecuta el recolector: simula disminuciones, encuentra los grupos realmente inalcanzables y los 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());
?>

Control del recolector

Puede controlar el recolector: gc_disable()/gc_enable() lo activan o desactivan, mientras que gc_collect_cycles() fuerza una pasada y devuelve la cantidad de elementos liberados. En trabajos por lotes críticos para el rendimiento, a veces se desactiva el recolector durante una fase intensiva y se ejecuta una sola recolección al 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";
?>

Procesos de larga duración

En demonios de CLI, consumidores de colas y procesos de Swoole/RoadRunner, la memoria no se recupera al finalizar la solicitud, ya que el proceso permanece activo durante horas. Preste atención a las cachés estáticas que crecen sin límite, los ciclos que se acumulan y los recursos que no se cierran. Supervise el consumo con memory_get_usage(true) y reinicie periódicamente los procesos.

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

Para almacenar datos en caché o asociarlos a un objeto sin mantenerlo activo, utilice WeakReference (8.0) y WeakMap (8.0). Una entrada de WeakMap no incrementa el conteo de referencias del objeto clave, por lo que el objeto puede recolectarse y su entrada se elimina automáticamente; es ideal para tablas auxiliares de metadatos que no deben provocar fugas.

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

Cadenas internadas e inmutables

Las cadenas literales del código fuente están internadas: se almacenan una sola vez, se comparten y no se cuentan referencias de ellas ni se liberan durante la solicitud. OPcache mueve las cadenas internadas y los op_arrays inmutables (almacenados en caché) a la memoria compartida entre solicitudes. Por eso copiar cadenas literales es, en la práctica, gratuito.

Límite de memoria y asignador

PHP utiliza su propio asignador emalloc (Zend Memory Manager), superpuesto al asignador del sistema, para contabilizar el uso por solicitud frente a memory_limit. Al alcanzar el límite, se produce un error fatal. El valor true en memory_get_usage(true) informa de los bloques realmente asignados por el sistema operativo, no solo de lo que mantiene actualmente el script.

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

Comprobación rápida

¿Por qué el conteo de referencias por sí solo puede provocar fugas en algunos objetos?

Resumen

PHP almacena los valores en zvals; los escalares se almacenan por valor, mientras que los tipos complejos se asignan en el montón y tienen conteo de referencias, con copia al escribir. El conteo de referencias libera memoria de forma determinista al llegar a cero, pero no puede recuperar los ciclos, por lo que se ejecuta un recolector de marcado y barrido sincrónico cuando se llena el búfer de raíces (o mediante gc_collect_cycles()). Utilice WeakMap/WeakReference para asociaciones no propietarias y supervise el crecimiento sin límite en los procesos de larga duración.

Preguntas frecuentes

¿La lección «Gestión de memoria y recolección de basura» es gratis?

Sí — el texto completo de «Gestión de memoria y recolección de basura» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de PHP Academy, actualiza a CoddyKit PRO. El curso de PHP Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Gestión de memoria y recolección de basura»?

Comprenda cómo funcionan los zvals, el recuento de referencias y el GC Practicas PHP Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar PHP Academy?

No se requiere experiencia previa. PHP Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Gestión de memoria y recolección de basura»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de PHP Academy?

Sí. Cada lección de PHP Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Cómo funciona Zend Engine
  2. Gestión de memoria y recolección de basura
  3. OPcache y compilación JIT
  4. Escritura de una extensión básica de PHP en C
← Volver a PHP Academy