
PHP8 上級/準上級試験の認定模擬問題として、プライム・ストラテジー様のPRIME STUDYが認定されているようです。ただ、この模擬問題には解説がありませんので解説をまとめていこうと思います。シリーズ第21回目です。

PHP初級レベルから脱却して中級/上級レベルにいきたい人にピッタリの内容ですね。

今回はPHPのメモリ管理と参照カウントに関する問題です。オブジェクトの参照カウントやコピーオンライトの挙動、xdebug_debug_zval() 関数を用いたメモリ使用状況の確認方法など、メモリ管理の基本的な理解が問われます。しっかりと押さえておきましょう!
PRIME STUDY認定模擬問題のリンクはこちらです → https://study.prime-strategy.co.jp/
問題文
PHP のメモリ消費 に関する説明の中で、誤っているものを1つ選びなさい。
また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。
declare(strict_types=1);
error_reporting(-1);
(1)
PHP では、「プログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に解放する」いわゆるガベージコレクションが機能として存在する。
PHP のガベージコレクションは「参照カウント法」という方式で管理されている。
参照された数は、Xdebug がインストール済みであれば xdebug_debug_zval() 関数によって得る事ができる。
そのため、以下のコード
$obj = new stdClass();
xdebug_debug_zval('obj');
$obj2 = $obj;
xdebug_debug_zval('obj');
xdebug_debug_zval('obj2');
unset($obj);
xdebug_debug_zval('obj2');
を実行すると、結果は次のとおりとなる。
obj: (refcount=1, is_ref=0)=class stdClass { }
obj: (refcount=2, is_ref=0)=class stdClass { }
obj2: (refcount=2, is_ref=0)=class stdClass { }
obj2: (refcount=1, is_ref=0)=class stdClass { }
(2)
PHP の変数で、参照された数は、Xdebug がインストール済みであれば xdebug_debug_zval() 関数によって得る事ができる。
しかし int や string などの型の変数は、代入演算子 = によって「元の変数を新しい変数にコピーする (値による代入)」ために、通常の代入では refcount は増えない。
一方で参照による代入 & をすると refcount が増える。
そのため、以下のコード
$i = 1;
xdebug_debug_zval('i');
$i2 = $i;
xdebug_debug_zval('i');
$i3 = &$i;
xdebug_debug_zval('i');
を実行すると、結果は次のとおりとなる。
i: (refcount=0, is_ref=0)=1
i: (refcount=0, is_ref=0)=1
i: (refcount=2, is_ref=1)=1
(3)
PHP の変数で、参照された数は、Xdebug がインストール済みであれば xdebug_debug_zval() 関数によって得る事ができる。
オブジェクトを clone した場合は「オブジェクトのプロパティを 全てシャローコピーする」が、コピーオンライトによって内部的には参照が用いられているために、値を変更するまでの間は一時的に refcount が増える。
そのため、以下のコード
$obj = new stdClass();
xdebug_debug_zval('obj');
$obj2 = clone $obj;
xdebug_debug_zval('obj');
$obj2->tset = 'value';
xdebug_debug_zval('obj');
を実行すると、結果は次のとおりとなる。
obj: (refcount=1, is_ref=0)=class stdClass { }
obj: (refcount=2, is_ref=0)=class stdClass { }
obj: (refcount=1, is_ref=0)=class stdClass { }
(4)
PHP の変数はコピーオンライトが使われているため、参照ではないコピーであっても、コピーのタイミングではメモリはほとんど消費される事がない。
ただしコピー先の値に変更が加わると、そのタイミングで「実態がコピーされる」ために、一気にメモリが消費される。
そのため、以下のコード
$awk = range(0, 1000000);
var_dump(memory_get_usage(true));
$awk2 = $awk;
var_dump(memory_get_usage(true));
$awk2[] = 'v';
var_dump(memory_get_usage(true));
を実行すると、結果は次のとおりとなる。(値は環境によって変わる)
int(35655680)
int(35655680)
int(69214208)
解説
選択肢1の解説
正しいです。
xdebug_debug_zval() 関数はオブジェクトの参照カウント(refcount)を表示するため、参照カウントが増減する様子を確認することができます。$obj2 = $obj のように参照渡しを行った場合、参照カウントが増加することが確認できます。
選択肢2の解説
正しいです。
$i2 = $i の場合、値渡しなので参照カウントは増えませんが、$i3 = &$i の場合、参照渡しになるので参照カウントが増加します。この違いは xdebug_debug_zval() を使って確認できます。
選択肢3の解説
誤りです。クローン後にオブジェクトのプロパティが変更されることで、参照カウントが増えるのではなく、メモリがコピーされるため、参照カウントは変わらずメモリ使用量が増加します。
選択肢4の解説
正しいです。
PHP はコピーオンライト(copy-on-write)を使用しており、参照ではないコピーを作成しても、実際にはメモリの消費は増えません。コピー先の値に変更が加わるまで、オリジナルとコピーは同じデータを指しています。そのため、変更が加わった時点でメモリが一気に消費されます。
正解選択肢
以上より選択肢3が正解となります。
他の問題へ
模擬問題(1)インストール
模擬問題(2)変数の型
模擬問題(3)クラス
模擬問題(4)メソッドその1
模擬問題(5)メソッドその2
模擬問題(6)リファレンス
模擬問題(7)名前空間
模擬問題(8)エラー
模擬問題(9)定義済みのインターフェイスとクラスおよび SPL インターフェイス
模擬問題(10)SPL
模擬問題(11)定義済の変数
模擬問題(12)PHP7.0.xからPHP7.1.xへの移行
模擬問題(13)可変変数
模擬問題(14)PHP7.3.xからPHP7.4.xへの移行
模擬問題(15)PHP7.4.xからPHP8.0.xへの移行
模擬問題(16)制御構造
模擬問題(17)XSS(クロスサイトスクリプティング)
模擬問題(18)ファイルアップロード
模擬問題(19)推測困難なトークン
模擬問題(20)セッション
模擬問題(21)PHP のメモリ消費
模擬問題(22)関数その1
模擬問題(23)関数その2
模擬問題(24)関数その3
模擬問題(25)関数その4
模擬問題(26)関数その5
模擬問題(27)関数その6
模擬問題(28)関数その7
模擬問題(29)関数その8
模擬問題(30)関数その9
コメント