PHP的引用计数

思考一下下面这段代码的执行过程和结果

$a和$b应该有相同的值

首先,PHP的变量和值在底层实现上是分开存储的, 变量的名称保存在符号表中,变量的值则是通过一个zval的结构来保存,然后他们通过指针关联起来。PHP中在创建变量$a时,为“dog”这个值分配了内存空间,$a则存在符号表中,通过指针指向“dog”内存位置;$a的值又赋值给$b,  所以$a和$b拥有了相同的值,为了节约内存, 这个时候PHP内部是做了特殊处理的,这种赋值其实只是在符号表中加入了$b, 然后也指向了“dog”这个内存位置。减少了因复制导致的内存浪费。

$a的销毁不应该影响$b的值

这个应该是一个合理的思考,$a的销毁不应该影响$b的值,那么销毁$a都做了哪些操作呢?根据上面的逻辑,$a的销毁其实是把变量名称从符号表中删除,并且去掉了其到“dog”这个内存地址的指针。

“dog”这个值的内存空间如何释放

这个时候如果我们销毁掉$b, 那么按照上面的逻辑符号表中去掉$b,  那么值的内存空间什么时候释放呢?

引用计数

通过以上的思考我们终于可以正大光明的引入这个概念了,说到引用计数,需要首先了解一下zval结构:

其中refcount__gc就是用来实现变量的引用计数的,每引用一次+1, 减少一次引用就-1,结合上面的例子,创建$a时refcount__gc 初始化为 1, $b的创建使的引用计数增加到2,接着$a的销毁计数减小到1, 如果再有$b的销毁,那么引用计数就变为了0, 这个时候就触发了垃圾回收操作,将值的内存空间回收。

变量的修改操作会不会相互影响

根据以上的原理,$a和$b都指向了同一个内存地址,那么如果修改其中任何一个值,另一个会不会跟着发生变化呢?我们当然是期望不会相互影响。

写时复制

在修改$a的值的时候,PHP底层做了一个特别的处理,它会判断一下refcount__gc的值,如果是大于1,会复制一份zval并分配一个新的内存地址给它,当然$a指向的地址要发生变化了,那么原来的zval的引用计数就需要-1。

直接引用赋值

在PHP中我们经常做一些引用赋值的操作

这种情况和上面我们讨论的区别是什么呢?

引用标记

为了区分这种引用赋值,在zval的结构中引入了is_ref__gc, 如果是引用赋值那么该值会设置为1, 否则默认为0。在修改$a的时候,如果是是引用赋值,那么就不需要复制zval了, 直接改变原来的zval对应的值,那么也就是说这个时候$b的值也会保持和$a同步变为’cat’。

变量复制和引用赋值同时存在的情况

按照顺序,首先$a和$b共同指向一个zval, 后面$c的引用赋值使得$a不得不和$b分开,否则就乱了, 这个时候zval强制复制出新的zval和$a,$c 关联上,而$b还是用原来的zval。



发表评论

电子邮件地址不会被公开。