PHP 变量存储初探
前几天在排查一个系统BUG的时候发现一段很有意思的代码
$obj=Obj()::where()->get();//这里是eloquent方法,大概15K条数据
foreach($obj as $k=>$v){
bar();
if($v->user){//eloquent 的has one
foo();
}
}
这段代码撑爆了单线程128M的内存限制。原因就在循环中$v->user
没有释放内存。根据PHP手册对foreach的描述,我们可以得知这里的$v
是拷贝赋值,并不是引用赋值。那么为什么每次循环结束的时候,这些内存没有被回收?
答案在于php存储变量的方式中。PHP中的变量存在一个叫zval的结构体中,我们所用的$a
,$b
...都只是一个变量的标签,变量真正的值都存在zval容器中。其实每次循环结束$v
所占用的内存都已经被回收,但是销毁的 $v
只是一个符号,对象真正使用的空间并没有被回收。看完下面这段代码,就应该能很容易的了解到底发生了什么。
echo "<br/>----------- Init a -------------<br/>";
$a=new stdClass();
$a->foo=1;
xdebug_debug_zval('a');
echo "<br/>----------- Init b ------------<br/>";
$b=$a;
$b->foo="bar";
xdebug_debug_zval('a');
xdebug_debug_zval('b');
echo "<br/>----------- Unset b ------------<br/>";
unset($b);
xdebug_debug_zval('a');
这段代码将返回:
----------- Init a -------------
a:
(refcount=1, is_ref=0),
object(stdClass)[1]
public 'foo' => (refcount=1, is_ref=0),int 1
----------- Init b ------------
a:
(refcount=2, is_ref=0),
object(stdClass)[1]
public 'foo' => (refcount=1, is_ref=0),string 'bar' (length=3)
b:
(refcount=2, is_ref=0),
object(stdClass)[1]
public 'foo' => (refcount=1, is_ref=0),string 'bar' (length=3)
----------- Unset b ------------
a:
(refcount=1, is_ref=0),
object(stdClass)[1]
public 'foo' => (refcount=1, is_ref=0),string 'bar' (length=3)
这里销毁了$b
,但是却并没有将对象销毁掉。所以,在上面的的foreach中,orm对象不停的叠加user
中的属性,而一直没有被回收,最终撑爆了内存。
解决方案是我司zkq指点我的,他让我尝试下深拷贝,完美解决。
$obj=Obj()::where()->get();
foreach($obj as $k=>$v){
$value = clone $v;//克隆一个对象
bar();
if($value->user){//使用克隆的对象,循环结束立刻销毁
foo();
}
}
参考: