ホームページ  >  記事  >  バックエンド開発  >  PHP 参照カウントのメモリ管理メカニズムとガベージ コレクション メカニズム

PHP 参照カウントのメモリ管理メカニズムとガベージ コレクション メカニズム

不言
不言オリジナル
2018-04-03 16:49:321204ブラウズ

この記事では、PHP の参照カウントのメモリ管理メカニズムとガベージ コレクション メカニズムについて説明します。助けが必要な友人は参照してください。

参照の代入

$a = 'apple';
$b = &$a;

上記のコードでは、変数 a に文字列を割り当てます。次に、a の参照を変数 b に代入します。明らかに、この時点で指すメモリは次のようになるはずです:

$a -> 'apple' <- $b

a と b は同じメモリ領域 (変数コンテナ zval) を指します。これは var_dump($a, $b) を通じて取得します。 code> <code>string(5) "apple" string(5) "apple" 、これは予期された結果です。 var_dump($a, $b) 得到 string(5) "apple" string(5) "apple" ,这是我们预期的结果。

unset函数 与 引用计数

unset 函数

假如我想将 'apple' 这个字符串从内存中释放掉。我是这么做的:

unset($a);

但是通过再次打印 $a $b 两变量的信息,我得到了这样的结果:Notice: Undefined variable: astring(5) "apple" 。奇怪,$a $b 指向同一个变量容器,又明明将$a释放了,为什么$b还是'apple'

其实是这样的,unset()只是将一个变量符号a(指针)销毁了,并没有释放掉那个变量容器,所以执行完操作之后,内存指向只是变成了这样:

'apple' <- $b

引用计数

引用计数 (reference count)是每个变量容器中都会存放的一条信息,它表示当前变量容器正被多少个变量符号所引用。

正如之前的例子,unset()并没有释放变量所指向的变量容器,而只是将变量符号销毁了。同时,将变量容器中的 引用计数 减1,当引用计数为0时,也就是说当变量容器不被任何变量引用时,便会触发php的垃圾回收(错误),它便会被释放(正确)。

更正上述的一个小错误: 这种单纯的引用计数方式是 php 5.2 之前的内存管理机制,称不上是垃圾回收机制,垃圾回收机制是 php 5.3 才引入的,垃圾回收机制为的是解决这种单纯的引用计数内存管理机制的缺陷(即 循环引用导致的内存泄漏,下文会进行讲解)

回到正题,我们用代码来验证一下先前的结论:

$a = 'apple';
$b = &$a;

$before = memory_get_usage();
unset($a);
$after = memory_get_usage();

var_dump($before - $after);  // 结果为int(0),变量容器的引用计数为1,没有释放
$a = 'apple';
$b = &$a;

$before = memory_get_usage();
unset($a, $b);
$after = memory_get_usage();

var_dump($before - $after);  // 结果为int(24),变量容器的引用计数为0,得到释放

直接释放

那要怎样做才能真正释放掉 'apple' 所占用的内存呢?

利用上述方法,我们可以在 unset($a) 之后再 unset($b) ,将变量容器的所有引用都销毁,引用计数减为0了,自然就被释放掉了。

当然,还有更直接的方法:

$a = null;

直接赋值 null 会将 $a 所指向的内存区域置空,并将引用计数归零,内存便被释放。

脚本执行结束后的内存

对于一般的web程序来说(fpm模式下),php的执行是单线程同步阻塞型的,当脚本执行结束之后,脚本内使用的所有内存都会被释放。那么,我们手动去释放内存到底有意义吗?

其实关于这个问题,早有解答,推荐大家看一下鸟哥 @laruence 2012年发表的一篇文章:

请手动释放你的资源(Please release resources manually)

引用计数内存管理机制的缺陷:循环引用

现在我们来讲讲之前提到的引用计数内存管理机制的缺陷。

当一个变量容器的引用计数为0时,php会进行垃圾回收。但是,你可想过,有一种情况会导致一个变量容器的引用计数永远不会被减为0,举个例子:

$a = ['one'];
$a[] = &$a;

我们看到,$a数组第二个元素就是它本身。那么,存放数组的这个变量容器的引用计数为2,一个引用是变量a,另一个引用是这个数组的第二个元素 - 索引1

PHP 参照カウントのメモリ管理メカニズムとガベージ コレクション メカニズム

那么,如果这时我们 unset($a) ,存放数组的变量容器的引用计数会减1,但还有1个引用,就是数组的元素 1

unset 関数と参照カウント

unset 関数

文字列 'apple' をメモリから解放したいとします。これが私がやったことです: PHP 参照カウントのメモリ管理メカニズムとガベージ コレクション メカニズムrrreee しかし、 $a $b という 2 つの変数の情報を再度出力すると、次の結果が得られました: Notice: Unknown variable : astring(5) "apple"。奇妙なことに、$a $b は同じ変数コンテナを指していますが、$a は明らかに解放されています。なぜ $b は のままなのでしょうか。リンゴ '

実際には、これが当てはまります。 unset() は変数シンボル a (ポインター) を破棄するだけで、変数コンテナーを解放しません。そのため、操作が完了した後、メモリ ポインタは次のようになります。

rrreee

Reference Count

参照カウント (参照カウント) は、各変数コンテナに格納されている情報であり、現在の変数によって使用されている変数シンボルの数を示します。コンテナ。 前の例と同様に、unset() は変数が指す変数コンテナを解放せず、変数シンボルを破棄するだけです。同時に、変数コンテナの

参照数🎜を1減らします。参照数が0の場合、つまり、変数コンテナがどの変数からも参照されていない場合、 PHP のガベージ コレクションがトリガーされ (誤) 🎜、解放されます (正)。 🎜🎜🎜上記の小さなエラーの修正: この単純な参照カウント方法は、PHP 5.2 より前のメモリ管理メカニズムであり、ガベージ コレクション メカニズムとは PHP 5.3 でのみ導入されました。コレクションメカニズムは、この単純な参照カウントメモリ管理メカニズムの欠点 (つまり、循環参照によって引き起こされるメモリリーク、以下で説明します) を解決します 🎜🎜🎜 トピックに戻り、コードを使用して前の結論を検証します: 🎜rrreeerrreee🎜直接解放🎜🎜 それでは、'apple' が占有しているメモリを実際に解放するにはどうすればよいでしょうか? 🎜🎜上記のメソッドを使用すると、unset($a) してから unset($b) を実行して、変数コンテナへのすべての参照を破棄し、参照カウントを 0 に減らすことができます。 、自然にリリースされました。 🎜🎜もちろん、もっと直接的な方法もあります: 🎜rrreee🎜 null を直接代入すると、$a が指すメモリ領域が空になり、参照カウントがゼロにリセットされます。 . メモリが解放されます。 🎜🎜スクリプト実行終了後のメモリ🎜🎜 一般的なWebプログラム(fpmモード)の場合、PHPの実行はシングルスレッド同期ブロッキングであり、スクリプトの実行が終了すると、スクリプトで使用されていたすべてのメモリが解放されます。では、手動でメモリを解放するのは意味があるのでしょうか? 🎜🎜実際、この質問は長い間解決されてきましたが、2012 年に Brother Bird @laruence が公開した記事を読むことをお勧めします。 🎜
リソースを手動で解放してください (リソースを手動で解放してください)
🎜参照カウントのメモリ管理メカニズムの欠陥: 循環参照🎜🎜 ここで、前述した参照カウントのメモリ管理メカニズムの欠陥について話しましょう。 🎜🎜変数コンテナの参照カウントが0になると、PHPはガベージコレクションを実行します。ただし、考えたことはありますか。変数コンテナの参照カウントが 0 に減らない状況が存在します。例: 🎜rrreee🎜 $a array 要素はそれ自体です。次に、配列を格納する変数コンテナの参照カウントは 2 で、1 つの参照は変数 <code>a で、もう 1 つの参照は配列の 2 番目の要素 - インデックス 1 です。 。 🎜🎜🎜PHP 参照カウントのメモリ管理メカニズムとガベージ コレクション メカニズム🎜🎜🎜次に、unset( $a ) の場合、配列を格納する変数コンテナの参照数は 1 減りますが、参照はまだ 1 つあります。これは配列の要素 1 になります。次のように: 🎜🎜 🎜🎜🎜🎜🎜変数コンテナの参照カウントが 0 に変更されていないため、解放できません。また、現時点ではそれを参照している他の外部変数シンボルはなく、ユーザーは解放する方法がありません。この構造をクリアすれば、それは永遠にそこに残ります。 🎜🎜そのため、コード内にそのような構造や操作が多数ある場合、最終的にはメモリの損失やリークさえも発生します。 🎜循環参照🎜によりメモリが解放できない問題です。 🎜

幸いなことに、fpm モードでは、要求されたスクリプトの実行が終了すると、PHP はこの構造を含むスクリプトで使用されているすべてのメモリを解放します。しかし、それがデーモンプロセス下の php プログラムの場合はどうなるでしょうか?スウールなど。 php で解決する必要があるこの緊急の問題 (すでに解決されています。以下を参照してください)。

PHP 5.3.0で導入された同期アルゴリズム

伝統的に、過去にPHPで使用されていた参照カウントメモリメカニズムは、循環参照のメモリリークを処理できません。ただし、5.3.0 PHP の使用法に関する記事「参照カウント システムの同時サイクル コレクション」にある同期アルゴリズムは、このメモリ リークの問題を解決します。このアルゴリズムは、PHP のガベージ コレクション メカニズムです。

特定のアルゴリズムの実装とプロセスは少し複雑です。ここでは詳しく説明しませんが、アルゴリズムのプロセスを説明する記事へのリンクもいくつか添付します:

http ://php.net/manual/zh/feat... 公式ドキュメント
http://www.cnblogs.com/leoo2s...
https://blog.csdn.net/phpkern...

最後に、私はまだNiao兄弟の記事を引用しています これらの2つの段落は問題を説明しています:

PHP5.2以前、PHPはリソース管理に参照カウントを使用していましたが、zvalの参照カウントが0の場合、循環参照が存在します。 ( 循環参照 ) しかし、Web スクリプトの特性と目的は実行時間が短く、長時間実行されないため、このような設計は Web スクリプト開発には問題ありません。循環参照によるリソース リークは最後に発生します。つまり、リクエストが終了したときにリソースを解放するのが救済措置 (バックアップ) です

ただし、PHP を使用する人が増えるにつれて、多くの人がいくつかのバックグラウンド スクリプトで PHP を使用するようになります。スクリプトが長時間実行されると、参照カウントが時間内に未使用のリソースを解放できなくなり、最終的にはスクリプトがメモリ不足になって終了するため、PHP5.3 以降では導入されました。 GC、つまりユーザーが解決できない問題を解決するために GC を導入しました

以上がPHP 参照カウントのメモリ管理メカニズムとガベージ コレクション メカニズムの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。