作者rickysu (Ricky)
看板PHP
标题Re: [分享] PHP GC 机制
时间Tue Jun 4 09:32:44 2013
※ 引述《rickysu (Ricky)》之铭言:
: 题外话: 搞了好久终於注册好 PTT 了。
: ======================================
: 前阵子看到某篇文章提到,要回收物件时,
: 使用 $obj = null 会马上回收,unset($obj) 则会比较慢。
: 在解答之前,先来玩个小游戏。
: <?php
: class test
: {
: public function __destruct()
: {
: echo "object of test is dead\n";
: }
: }
: $test = new test();
: $test = null;
: die("program is end\n");
: 执行结果
: object of test is dead
: program is end
: 很符合结果 $test = null 先执行 GC (destruct) 接着 die output
: ===================
: <?php
: class test
: {
: protected $me;
: public function __contruct()
: {
: $this-> me = $this;
: }
: public function __destruct()
: {
: echo "object of test is dead\n";
: }
: }
: $test = new test();
: unset($test);
: die("program is end\n");
: 执行结果
: program is end
: object of test is dead
: unset 没有进行 GC,一直到程式结束後,才开始进行 GC。
: 疑 unset 没有进行 GC ??!!
: 好玩的事情发生了,难道真的是 unset 是看心情 GC 的??
: 其实上面的范例即使改成 $test = null; 执行结果也是一样的。
: 这边就先卖个关子,明天再来解答为什麽会有这个结果,以及该怎麽避免这样的问题。
PHP 在判断物件是否该被 GC 启用了 reference counting 的机制来作为判断。
简单的说,当某个物件被参照时就把他的 refcount+1 。
例如 $a = new test();
他是把 test 物件 refcount + 1,
$a 只是一个指向 test 物件的指标。
如果这时候又有一个
$b = $a;
php 底层则是把 test 物件的 refcount 再度 + 1。
这也就是为什麽 php 从 5.0 之後物件都是 by referenc 的原因。
一方面为了节省记忆体,一方面则是加快物件引用的处理速度。
那 PHP 怎麽判断一个物件该被执行 GC。
当一个变数被赋予新的数值,或是被 unset ,就会把他对应物件的 refcount-1。
当物件的 refcount 归 0 时,进行 GC (先执行 destructor,再将 memory 回收)。
看起来 reference counting 的机制运作得很完美,
但是某天某位仁兄在 PHP 的 bug report 中发了一个 bug。大致的问题是这样。
他发觉当某个情况时 PHP 会迅速的吃光所有的 memory。
(注: PHP 底层处理 array 跟 object 使用的机制是相同的,都是透过 hashtable )
while(true){
$a = array(1,2,3,4);
$a[] = &$a;
}
照理来说 $a 被重新 assign array 时,原本的 memory 应该要被释放掉才对。
可是实际状况却不是这样。 Why??
我们来看一下前面提到的第二个例子
$test = new test();
首先 constructor 中指定了
$this->me = $this; 这时候 test 物件的 refcount => 1
接着 $test = new test(); refcount = 2
然後执行 unset($test); refcount = 1,因为参照的变数被 unset 所以 -1
这时候好玩的事情发生了 refcount 尚未归 0,所以不会被 GC ,
但是已经没有任何变数参照到这个物件了。
这个物件就永远变成垃圾而且无法被回收,除非程式结束才会被回收。
那这个问题在 PHP 5.2 以前的版本是无解的,幸好 PHP 大部分的状况都是一次 request
後就结束,程式结束後 memory 还是会被 OS 回收。
但是随着 framework 的盛行,
PHP 中物件被环形引用($a->next = $b; $b->next = $a;)的状况越来越多,
因此 PHP 5.3 开始重新设计整个 GC 机制。
如果有仔细看过 PHP 5.3 的 release note ,除了 namespace 之外
最重大的变动就是多了 gc_collect_cycles, gc_enable, gc_disable。
PHP 5.3 中引入了一个新的演算法,专们用来对付这种环形引用所留下来的垃圾。
如果想看看他是怎运作的可以参考这篇
http://www.php.net/manual/en/features.gc.collecting-cycles.php
不过这种演算法每次都得递回整个引用的物件,
造成的代价相当高。(新版本跑得比之前版本慢是不被允许的)
因此PHP 5.3 会将每个可能需要被 GC 的物件先丢到一个 list 中。
这边所谓的 "可能需要被GC" 是指,只要是物件或是array,
当他被 unreference 时就会先丢到这个暂存 list 中。
当 list 满的时候一次进行 GC (印象中没记错list只能容纳1000个待GC的物件)。
如果想要强制进行 GC 只要呼叫 gc_collect_cycles() ,就会马上进行 GC。
希望这篇文章能让大家对 PHP 的 GC 执行时机有些帮助。
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 220.130.136.115
1F:推 gpmm:啧啧啧,把旧文拿出来充数可不行唷 XDD 06/04 16:09
2F:→ rickysu:哈...被发现了,看来得生点有趣的东西。 06/04 16:30
3F:推 chaoms:推第二篇的GC观念和使用说明。但小熟zend engine+只看 06/04 18:19
4F:→ chaoms:第一篇的话会有误解 06/04 18:19
5F:→ xxxzzz:有人用$this->me=$this,也会有原PO讲的情形吗?_ 06/04 22:53
6F:→ xxxzzz:我用$test->me=$test,才会有这种情形,我看官网也是写这样 06/04 22:55
7F:→ xxxzzz:不是写在 __contruct 里面? (我的测试环境php5.2.17) 06/04 22:55
8F:→ rickysu:还没在PHP5.2上实验 ,不过基本的精神是环形引用。 06/05 09:07
9F:→ rickysu:如果是在web server上跑,基本上没啥 memory leak的问题。 06/05 09:10