Skip to content

11 garbage collector gb en php

@intelguasoft edited this page Jan 22, 2020 · 1 revision

Curso Master PHP

Recolector de basuras

El garbage collector, o recolector de basuras, es un sistema que permite a PHP limpiar la memoria cuando así lo considere. También es posible activar y desactivar esta característica desde el código para precisar tiempos y reducir la memoria en aplicaciones concretas.

Indice de contenido

  1. Almacenamiento de variables en la memoria
  2. Valores de tipo compuesto
  3. Problemas de limpieza (anteriores a PHP 5.3)
  4. Recolección de referencias cíclicas

1. Almacenamiento de variables en la memoria

Una variable en PHP se almacena en un contenedor llamado zval. Éste contiene el tipo de variable, su valor y dos bits de información:

  • El primer bit es _is_ref _que contiene un boolean que indice si la variable es parte o no del conjunto de referencias. Con este bit el motor de PHP diferencia entre variables normales y referencias.
  • El segundo bit es refcount, que contiene el número de variables (también llamadas símbolos) que apuntan a este contenedor zval. PHP permite al usuario definir referencias (con &) y el contador permite optimizar el uso de memoria.

Los símbolos se almacenan en una tabla de símbolos, una por cada ámbito (hay un ámbito para el script principal, y otro por cada función y método).

Un contenedor zval se crea cuando se crea una variable con valor constante, por ejemplo:

<?php
$x = "hola";

El nuevo nombre de símbolo, x, se crea en el ámbito actual. Se crea un nuevo contenedor de variable de tipo string y valor hola. El bit is_ref será falso pues no contiene ninguna referencia. El bit refcount vale 1 ya que sólo hay un símbolo que haga uso de este contenedor de variables (nótese que si refcount vale 1, is_ref siempre será false). Si tienes instalado Xdebug puedes mostrar esta información mediante la función xdebug_debug_zval():

<?php
xdebug_debug_zval('x'); // muestra x: (refcount=1, is_ref=0), string 'hola'

Al asignar la variable a otra se incrementa refcount:

<?php
$x = "hola";
$y = $x;
xdebug_debug_zval('x'); // muestra x: (refcount=2, is_ref=0), string 'hola'

refcount vale 2 ya que el mismo contenedor de variables está vinculado tanto por x como por y. PHP no copia el contenedor de variable cuando no es necesario. Los contenedores de variables se detruyen cuando refcount se queda a cero. refcount decrece en uno cuando alguno de los símbolos que lo vinculan abandona su ámbito (como cuando finaliza una función) o cuando se llama a unset():

<?php
$x = "hola";
$y = $z = $x;
xdebug_debug_zval('x'); // x: (refcount=3, is_ref=0), string 'hola'
unset($y, $z);
xdebug_debug_zval('x'); // x: (refcount=1, is_ref=0), string 'hola'

Si se llama a unset($x) el contenedor de variable, tanto el tipo como el valor, se eliminan de la memoria.

2. Valores de tipo compuesto

Con arrays y objetos esto es algo más complejo. En lugar de un valor escalar, los arrays y objetos almacenan sus propiedades en su propia tabla de símbolos.

<?php
$x = array('animal' => 'perro', 'edad'=> 10);
xdebug_debug_zval('x');
/* Devuelve:
x: (refcount=1, is_ref=0), array(
    'animal' => (refcount=1, is_ref=0), string 'perro'
    'edad' => (refcount=1, is_ref=0), int 10
)

Se crean tres contenedores: x, animal y edad. Las normas son similares a la hora de aumentar y disminuir refcounts. Si añadimos otro elemento, y fijamos su valor a un elemento ya existente:

<?php
$x = array('animal' => 'perro', 'edad' => 10);
$x['servivo'] = $x['animal'];
xdebug_debug_zval('x');
/* El resultado ahora sería
x: (refcount=1, is_ref=0), array(
    'animal' => (refcount=2, is_ref=0), string 'perro'
    'edad' => (refcount=1, is_ref=0), int 10
    'servivo' => (refcount=2, is_ref=0), string 'perro'
)

Tanto animal como servivo apuntan al mismo contenedor, cuyo refcount vale 2 (aunque xdebug muestre dos contenedores zval de valor perro, se refiere al mismo).

Eliminar un elemento del array viene a ser lo mismo que eliminar un símbolo de un determinado ámbito. Cuando se hace, el refcount del contenedor al que apunta el elemento del array decrece. Al igual que antes, si refcount llega a cero, se elimina de la memoria.

<?php
$x = array('animal' => 'perro', 'edad' => 10);
$x['servivo'] = $x['animal'];
unset($x['animal'], $x['edad']);
xdebug_debug_zval('x');
/* Devuelve:
x: (refcount=1, is_ref=0), array(
    'servivo' => (refcount=1, is_ref=0),string 'perro'

Utilizando el operador de referencia, la cosa cambia:

<?php
$x = array('uno');
$x[] = &$x;
xdebug_debug_zval('x');
/* Devuelve:
x: (refcount=2, is_ref=1), array(
    0=>(refcount=1, is_ref=0, string 'uno'
    1=>(refcount=2, is_ref=1),&array
)

Tanto la variable de tipo array x como su segundo elemento 1 apuntan a un contenedor de variables que tiene un refcount de 2.

3. Problemas de limpieza (anteriores a PHP 5.3)

Si eliminamos una variable se elimina el símbolo, y el contador de referencias del contenedor de variables al que apunta decrece en uno. Si eliminamos $x, el contador de referencias del contenedor de variables al que apuntan x y 1 decrece en uno.

A pesar de que no hay ningún símbolo que apunte a esta estructura, no se puede limpiar porque el elemento 1 del array todavía apunta al mismo array. Al no haber ningún símbolo externo que apunte a él, no hay ninguna forma con la que el usuario pueda eliminar esta estructura, por lo que ocurre una fuga de memoria. PHP limpiará esta estructura de datos al finalizar la petición, pero antes ocupará un espacio en la memoria. Es frecuente que ocurra con objetos ya que siempre se usan por referencia.

Esto no resulta un problema cuando ocurre pocas veces, pero cuando ocurre miles, comienza a ser un problema.

Con la recolección de referencias cíclicas, a partir de PHP 5.3, esto se solventa.

4. Recolección de referencias cíclicas

El algoritmo se encuentra en este artículo.

Si se incrementa un refcount significa que se sigue usando el contenedor, no es basura. Si decrece y alcanza el valor de 0, se elimina automáticamente, lo que significa que la recolección de ciclos sólo se lleva a cabo cuando un parámetro refcount decrece a un valor que no sea cero.

Es posible comprobar qué partes son basura comprobando si se puede decrementar en uno sus refcount, para después comprobar cuáles han alcanzado a cero.

Para evitar llamar al comprobador de ciclos de basura en cada decremento de un refcount, el algoritmo pasa todos los posibles roots (zvals) al root buffer. Sólo cuando el root buffer está lleno, comienza el proceso de comprobación de todos los zvals que están dentro.

Por defecto, el recolector de basuras en PHP está habilitado (se puede cambiar en php.ini con zend.enable_gc). Cuando está habilitado, el algoritmo que busca ciclos se ejecuta cada vez que se llena el root buffer. Éste tiene un tamaño de 10.000 posibles roots.

Desde el código es posible habilitar o deshabilitar el mecanismo recolector de basura mediante las funciones gc_enable() o gc_disable(). La llamada a estas funciones tiene el mismo efecto que habilitar o deshabilitar el mecanismo en la propia configuración. También se puede forzar la recolección de ciclos sin que esté lleno el root buffer, llamando a gc_collect_cycles(), que devuelve el número de ciclos recolectados por el algoritmo.

La razón por la que se puede habilitar o deshabilitar el mecanismo, o iniciar ciclos de recolección a mano, es porque puede que haya partes en una aplicación que necesiten mucha precisión de tiempo. Lo más prudente si se llama a gc_disable() es llamar a gc_collect_cycles() para que libere la memoria por posibles raíces ya registradas en el root buffer.

Anterior Siguiente

Indice de contenidos

Básicos Sintaxis básica
Operadores
Operadores bit a bit
Variables
Estructuras de control
Constantes y constructores base
Espacio de nombres
Extensiones
Configuraciones
Variables al descubierto
Recolector de basuras
Rendimiento (Performance)
Funciones Funciones
Argumentos en funciones
Funciones variables
Valores por referencia en funciones
Funciones que devuelven algo
Ámbito de variables
Funciones anónimas y closure's
Cadenas y patrones Las comillas y las cadenas de caracteres
Heredoc y Nowdoc
Comparando cadenas de caracteres
Extracción en cadenas de caracteres
Análisis en cadenas de caracteres
Reemplazos en cadenas de caracteres
Formato de cadena de caracteres
Expresiones regulares (RegEx)
Codificación de caracteres
Codificación en cadenas de caracteres
Arreglos (Array's) Arreglos
Arreglos asociativos
Iterar arreglos
Funciones de arreglos
SPL Arreglos mediante objetos
Conversión de arreglos
Archivos (I/O) Manejo de archivos
Lectura de archivos
Escritura de archivos
Funciones del sistema de archivos
Socket's y archivos
Streams (?)
Seguridad Concepto y funcionamiento CGI
Configurando la seguridad
Seguridad en Sesiones
Ataques XSS
Ataques CSRF
Ataques SQLInjection
Ataques CodeInjection
Ataques EmailInjection
Filtrado de datos de entrada
Escape de datos de salida
Encriptación y contraseñas
Seguridad en el almacenamiento de datos
Seguridad en la subida de datos
SSL y OpenSSL
Base de datos Principios básicos SQL
Los joins en SQL
Principales funciones SQL
Preparando sentencias SQL
Transacciones en SQL
Algo de PDO
Programación Orientada a Objetos Instancias de clases
Modificadores y herencia de clases
Interfaces
Excepciones
Auto-carga (Autoload)
Reflexión (Reflection)
Determinación de tipos (Type hinting)
Constantes de clase
Enlace estático de ejecución (Late Static Binding)
Métodos mágicos
Librería estándar PHP (SPL)
Generadores (Generators)
Traits
Clases abstractas
Formatos de información Algo de XML
Algo de SimpleXML
Algo de XML Parser
Algo de PHP DOM
Algo de Web Services
Web Services con SOAP
Algo de REST
Algo de JSON
Formato de fecha y hora
Características web's Sesiones
Formularios
Métodos GET y POST
Cookies
Protocolo HTTP y sus headers
Autenticación HTTP
Códigos de estado HTTP
Referencias Referencias
Recopilación
Conclusión
Clone this wiki locally