PHPのzvalと変数の作成を見るの記事で、PHPの変数周りの内容を見た。

前回の内容を踏まえて、PHPのガベージコレクション(以後、GCと略す)を学ぶための準備をしていく。


PHPのガベージコレクションを学ぶにXdebugを入れるの記事でPHPの変数の詳細を確認できるxdebug_debug_zval関数を使用できるようにして、

/path/to/dir/xdebug.php

<?php
$a = new stdClass();
xdebug_debug_zval('a');

$ php /path/to/dir/xdebug.php
a: (refcount=1, is_ref=0)=class stdClass {  }

$aに標準クラスであるstdClassの値を入れた時のzvalを確認してみた。


注目すべきは、zvalの値でrefcount=1とis_ref=0というものがあるということ。

refcountの値がPHPのGCに大きく関与しているらしい。




はじめに

$a = new stdClass();

がzvalの視点ではどうなるか?を整理してみる。


冒頭のシンボル + zval + zend_valueをひとまとめにしてzend_valueと表示する事にして、

新たに設けたzvalのzend_valueにメモリの別の場所に設けたstdClassオブジェクトをポインタで繋げる。

ガベージコレクションを見るにあたって、上記のシンボル + zval + zend_value + zend_objectの繋がりが大事になる。

※整数型であるIS_LONGはzend_valueでポインタではなく、直接値を持つ




話を進める前にガベージコレクションについて触れておく。

ガベージコレクションとはコンピュータ・プログラムが動的に確保したメモリ領域、つまりは変数等で用意したメモリ領域のうち、不要となった領域を自動的に解放する機能を指す。

ガベージコレクション - Wikipedia


ここで疑問になるのが、ガベージコレクションは何をもって不要になったメモリ領域があると判断するのか?

PHPでは参照カウント法というものが採用されているらしく、冒頭のxdebug_debug_zval関数で見たrefcountの値が大事になってくる。


参照カウント法を見るために下記のコードを作成してみた。

<?php
$a = new stdClass();
$c = $b = $a;
xdebug_debug_zval('c');

単純に考えると、どの変数にもstdClassのオブジェクトが格納されている事になるが、xdebug_debug_zvalの出力内容は、

c: (refcount=3, is_ref=0)=class stdClass { }

となる。


図で表すと、

$a、$bと$cのどれも$aの時にメモリを確保して作成したstdClassの値を持っており、xdebug_debug_zvalで$cの値を確認した時にrefcount=3となっていた。

PHP: 参照カウント法の原理 - Manual




続いて、下記のようなコードで値を確認してみる。

<?php
$a = new stdClass();
$c = $b = $a;

//$aには文字列
$a = "hoge";

//$bには整数
$b = 123;
xdebug_debug_zval('c');

$aの値が別の型に上書きされ、$cはどの値を参照すればわからなくするといった状態にした後にxdebug_debug_zvalで$cを確認してみると、

c: (refcount=1, is_ref=0)=class stdClass { }

となった。


図で表すと、

$aの値を$cにコピーしつつ、$aには文字列型の値を入れ、各変数とstdClassの値の関係を見ると、stdClassの値を持っている変数は一つになるので、refcount=1となる。

コピーオンライド - Wikipedia


この処理の続きで、$cをunsetするとstdClassのrefcountが0になるため、ガベージコレクションがstdClassを不要と見なし、stdClassの為に確保したメモリの解放を行うという仕組みになるらしい。

※$aから$cに値をコピーする際に$aの方のstdClassが不要になったけれども、それもガベージコレクションの対象になるのか?

PHP: 循環の収集 - Manual




最後にメモリの確認を行う。

<?php
echo "処理前:" . memory_get_usage() . "\n";

$a = new stdClass();
$c = $b = $a;

echo "unset前:" . memory_get_usage() . "\n";

unset($a);
unset($b);

echo "unset中:" . memory_get_usage() . "\n";

unset($c);

echo "unset後:" . memory_get_usage() . "\n";

//ガベージコレクション→メモリを解放した回数を出力
var_dump(gc_collect_cycles());

echo "gc後:" . memory_get_usage() . "\n";

上記のコードを作成し、実行してみた結果が下記になる。

処理前:399456
unset前:399528
unset中:399528
unset後:399488
int(0)  //ガベージコレクションの収集したサイクル数
gc後:399488

unset毎gc後の値が同じだけれども、これはstdClassと紐づく変数がなくなった時点で、stdClassのメモリ領域が自動で解放されたという解釈で良いのかな?

PHPでコードを書く時、不要になったオブジェクトはすぐにunsetするのが吉であるようだ。


追記

<?php
$a = 123;
$b = $a;
xdebug_debug_zval('b');
b: (refcount=0, is_ref=0)=123

zvalにポインタではなく直接値を持つよう型(整数型等)では今回の話は当てはまらないようだ