There seems to be no way to inspect the reference count of a specific class variable but you can view the reference count of all variables in the current class instance with xdebug_debug_zval('this');
Una variable PHP se almacena internamente en un contenedor llamado "zval". Un contenedor zval contiene, además del tipo de la variable y su valor, dos informaciones adicionales. La primera se llama "is_ref" y es un valor booléano que indica si una variable forma parte de una referencia o no. Gracias a esta información, el motor de PHP sabe diferenciar las variables normales de las referencias. Como PHP permite al programador utilizar referencias, mediante el operador &, un contenedor zval posee también un mecanismo de conteo de referencias para optimizar el uso de la memoria. Esta segunda información, llamada "refcount", contiene el número de variables (también llamadas símbolos) que apuntan a este contenedor zval. Todos los símbolos se almacenan en una tabla de símbolos, y hay una tabla por ámbito de visibilidad (scope). Hay un ámbito global para el script principal (el que se llama, por ejemplo, a través del navegador) y un ámbito por función o método.
Un contenedor zval se crea cuando se crea una nueva variable con un valor constante, como por ejemplo:
Ejemplo #1 Creación de un nuevo contenedor zval
<?php
$a = "new string";
?>
En este caso, el nuevo símbolo a
se crea en el ámbito global,
y se crea un nuevo contenedor con el tipo string y el valor
new string
. El bit "is_ref" se establece por omisión en false
ya que ninguna
referencia ha sido creada por el programador. El contador de referencias "refcount" se establece en
1
ya que solo hay un símbolo que utiliza este contenedor.
Es de destacar que las referencias (es decir, "is_ref" es true
) con "refcount"
1
, se tratan como si no fueran
referencias (es decir, como si "is_ref" fuera false
). Si ha
instalado » Xdebug, puede mostrar esta
información llamando a xdebug_debug_zval().
Ejemplo #2 Mostrar información zval
<?php
$a = "new string";
xdebug_debug_zval('a');
?>
El resultado del ejemplo sería:
a: (refcount=1, is_ref=0)='new string'
Asignar esta variable a otro símbolo incrementará el refcount.
Ejemplo #3 Incrementar el refcount de una zval
<?php
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
?>
El resultado del ejemplo sería:
a: (refcount=2, is_ref=0)='new string'
El refcount vale 2
aquí, ya que el mismo contenedor está ligado
tanto a a como a b. PHP es lo suficientemente
inteligente como para no duplicar el contenedor cuando no es necesario.
Los contenedores se destruyen cuando su "refcount" llega a cero. El
"refcount" se decrementa en uno cuando cualquier símbolo ligado a
un contenedor sale del ámbito (por ejemplo: cuando la función termina) o
cuando un símbolo se desasigna (por ejemplo: por la llamada a unset()).
El siguiente ejemplo lo demuestra:
Ejemplo #4 Decrementar el refcount de una zval
<?php
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
$b = 42;
xdebug_debug_zval( 'a' );
unset( $c );
xdebug_debug_zval( 'a' );
?>
El resultado del ejemplo sería:
a: (refcount=3, is_ref=0)='new string' a: (refcount=2, is_ref=0)='new string' a: (refcount=1, is_ref=0)='new string'
Si ahora se llama a unset($a);
, el contenedor zval, incluyendo
el tipo y el valor, será eliminado de la memoria.
Las cosas se complican en el caso de tipos compuestos como array y object. A diferencia de los valores escalares, los array y object almacenan sus propiedades en una tabla de símbolos que les es propia. Esto significa que el siguiente ejemplo crea tres contenedores zval:
Ejemplo #5 Creación de una zval array
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
?>
El resultado del ejemplo sería algo similar a:
a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=1, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42 )
O gráficamente
Los tres contenedores zval son: a, meaning, y number. Las mismas reglas se aplican para el incremento y la decrementación de los "refcounts". A continuación, se añade otro elemento al array, y se asigna su valor con el contenido de un elemento ya existente del array:
Ejemplo #6 Añadir un elemento ya existente al array
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
xdebug_debug_zval( 'a' );
?>
El resultado del ejemplo sería algo similar a:
a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=2, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42, 'life' => (refcount=2, is_ref=0)='life' )
O gráficamente
La salida Xdebug que vemos indica que el antiguo y el nuevo elemento del array
apuntan ahora ambos a un contenedor zval cuyo "refcount" vale 2
.
Aunque la salida XDebug muestra dos contenedores zval con el valor 'life', son los
mismos. La función xdebug_debug_zval() no muestra esto, pero se podría ver
mostrando también el puntero de memoria.
Eliminar un elemento del array es asimilable a la eliminación de un símbolo desde un espacio. Al hacerlo, el "refcount" del contenedor al que apunta el elemento del array se decrementa. Una vez más, si llega a cero, el contenedor zval se elimina de la memoria. El siguiente ejemplo lo demuestra:
Ejemplo #7 Eliminación de un elemento de array
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
unset( $a['meaning'], $a['number'] );
xdebug_debug_zval( 'a' );
?>
El resultado del ejemplo sería algo similar a:
a: (refcount=1, is_ref=0)=array ( 'life' => (refcount=1, is_ref=0)='life' )
Ahora, las cosas se vuelven interesantes si añadimos el array como elemento de sí mismo. Hacemos esto en el siguiente ejemplo, utilizando un operador de referencia para evitar que PHP cree una copia:
Ejemplo #8 Añadir el array como referencia a sí mismo como elemento
<?php
$a = array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );
?>
El resultado del ejemplo sería algo similar a:
a: (refcount=2, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=... )
O gráficamente
Se puede ver que la variable array (a) así como el segundo elemento
(1) apuntan ahora a un contenedor cuyo "refcount" vale
2
. Los "..." en la visualización indican una recursión, que, en este caso,
significa que el "..." apunta al array mismo.
Como antes, eliminar una variable elimina su símbolo, y el refcount del contenedor al que apuntaba se decrementa. Por lo tanto, si eliminamos la variable $a después de ejecutar el código anterior, el contador de referencias del contenedor al que apuntan $a y el elemento "1" se decrementará en uno, pasando de "2" a "1". Esto se puede representar como:
Ejemplo #9 Eliminación de $a
(refcount=1, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=... )
O gráficamente
Aunque ya no haya ningún símbolo en el espacio de variables actual que apunte a esta estructura, no puede ser limpiada, ya que el elemento "1" del array sigue apuntando a este mismo array. Como ya no hay ningún símbolo externo que apunte a esta estructura, el usuario no puede limpiarla manualmente; por lo tanto, hay una fuga de memoria. Afortunadamente, PHP destruirá esta estructura al final de la solicitud, pero antes de esta etapa, la memoria no se libera. Esta situación ocurre a menudo si se implementa un algoritmo de análisis u otras ideas donde se tiene un hijo que apunta a su padre. Lo mismo puede ocurrir, por supuesto, con los objetos, y es incluso más probable, ya que siempre se utilizan implícitamente por "referencia".
Esto puede no ser molesto si ocurre solo una o dos veces, pero si hay miles, o incluso millones, de estas fugas de memoria, entonces obviamente esto puede convertirse en un problema importante. Esto es particularmente problemático para los scripts que duran mucho tiempo, como los demonios para los cuales la solicitud nunca termina, o incluso en grandes suites de pruebas unitarias. Este último caso se encontró al lanzar las pruebas unitarias del componente Template de la biblioteca eZ Components. En algunos casos, la suite de pruebas requería más de 2Go de memoria, que el servidor de prueba no tenía realmente disponible.
There seems to be no way to inspect the reference count of a specific class variable but you can view the reference count of all variables in the current class instance with xdebug_debug_zval('this');
If a variable is not present in the current scope xdebug_debug_zval will return null.
Result of "Example #8 Adding the array itself as an element of it self" will be another for PHP7:
a: (refcount=2, is_ref=1)=array (
0 => (refcount=2, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)
insted of:
a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)
Internal value representation in PHP 7:
https://nikic.github.io/2015/05/05/Internal-value-representation-in-PHP-7-part-1.html
$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
ouputs with PHP 7.3.12 (cli)
a: (interned, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1
my php versoin : HP 7.1.25 (cli) (built: Dec 7 2018 08:20:45) ( NTS )
$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
output:
a: (refcount=2, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1
if $a is a string value, 'refcount' equal 2 by defalut.
my php version is PHP 7.1.6 (cli), when I run
$a = 'new string';
$b = 1;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
it shows:
a: (refcount=0, is_ref=0)='new string'
b: (refcount=0, is_ref=0)=1