前言:以前对PHP的GC只是了解了个大概,此次详细了解下PHP的垃圾回收机制(GC)。
介于网上大部分都是PHP5.X的GC,虽然 php5 到 php7 GC部分作出的改动较小,但我以为仍是一块儿写下来比较好
1、原理php
php5和php7的垃圾回收机制都是利用引用计数
2、php5和php7不一样点算法
一、PHP5标量数据类型会计数,PHP7标量数据类型再也不计数,不须要单独分配内存
二、PHP7的zval 须要的内存再也不是单独从堆上分配,再也不本身存储引用计数。
三、PHP7的复杂数据类型(好比数组和对象)的引用计数由其自身来存储。
3、变量在zval的变量容器中结构数组
zval中,除了存储变量的类型和值以外,还有is_ref字段和refcount字段 一、is_ref:是个bool值,用来区分变量是否属于引用集合。 二、refcount:计数器,表示指向这个zval变量容器的变量个数。
4、PHP5.3标量在zval容器例子php7
注意:php5.3中将一个变量 = 赋值给另外一个变量时,不会当即为新变量分配内存空间,而是在原变量的zval中给refcount加1。 只有当原变量或者发生改变时,才会为新变量分配内存空间,同时原变量的refcount减 1 。固然,若是unset原变量,新变量直接就使用原变量的zval而不是从新分配。&引用赋值时,原变量的is_ref 加1. 若是给一个变量&赋值,以前 = 赋值的变量会分配空间。测试
<?php $a = 1; xdebug_debug_zval('a'); echo PHP_EOL; $b = $a; xdebug_debug_zval('a'); echo PHP_EOL; $c = &$a; xdebug_debug_zval('a'); echo PHP_EOL; xdebug_debug_zval('b'); echo PHP_EOL;
结果以下:
a:(refcount=1, is_ref=0),int 1spa
a:(refcount=2, is_ref=0),int 1debug
a:(refcount=2, is_ref=1),int 1code
b:(refcount=1, is_ref=0),int 1对象
5、PHP7.X 标量在zval容器例子blog
<?php $a = 1; xdebug_debug_zval('a'); echo PHP_EOL; $b = $a; xdebug_debug_zval('a');
结果以下:能够看到标量(布尔,字符串,整形,浮点型)再也不计数了
6、PHP5.3复合类型数组和对象在zval容器例子
<?php $a = array( 'meaning' => 'life', 'number' => 42 ); xdebug_debug_zval( 'a' ); echo PHP_EOL; class Test{ public $a = 1; public $b = 2; function handle(){ echo 'hehe'; } } $test = new Test(); xdebug_debug_zval('test');
结果以下:能够看出,数组用了比数组长度多1个zval存储。数组分配了三个zval容器:a meaning number
a:(refcount=1, is_ref=0),
array
'meaning' => (refcount=1, is_ref=0),
string
'life' (length=4)
'number' => (refcount=1, is_ref=0),
int
42
test:(refcount=1, is_ref=0),
object(Test)[1]
public 'a' => (refcount=2, is_ref=0),
int
1
public 'b' => (refcount=2, is_ref=0),
int
2
7、PHP7.X复合类型数组和对象在zval容器例子
<?php $a = array( 'meaning' => 'life', 'number' => 42 ); xdebug_debug_zval( 'a' ); echo PHP_EOL; class Test{ public $a = 1; public $b = 2; function handle(){ echo 'hehe'; } } $test = new Test(); xdebug_debug_zval('test');
结果以下:能够明显的看到数组a的refcount=2,后经测试发现数组的refcount都是从2开始的
8、循环引用问题
一、PHP7.1效果
<?php $a = array('life'); xdebug_debug_zval( 'a' ); echo PHP_EOL; $a[] = &$a; xdebug_debug_zval('a');
能够看到,箭头方向表示的就是递归循环引用了
二、再看看5.3的效果
说明:在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断一个变量空间是否可以被释放的时候是依据这个变量的zval的refcount的值,
若是refcount为0,那么变量的空间能够被释放,不然就不释放,这是一种很是简单的GC实现。如今unset ($a),那么array的refcount减1变为1.如今无任何变量指向这个zval,
并且这个zval的计数器为1,不会回收。
结果:尽管再也不有某个做用域中的任何符号指向这个结构(就是变量容器),因为子元素“1”仍然指向数组自己,因此这个容器不能被清除 。
由于没有另外的符号指向它,用户没有办法清除这个结构,结果就会致使内存泄漏。
在php5.3的GC中,针对的垃圾作了以下说明: 1:若是一个zval的refcount增长,那么此zval还在使用,确定不是垃圾,不会进入缓冲区 2:若是一个zval的refcount减小到0, 那么zval会被当即释放掉,不属于GC要处理的垃圾对象,不会进入缓冲区。 3:若是一个zval的refcount减小以后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾,将其放入缓冲区。PHP5.3中的GC针对的就是这种zval进行的处理。
开启/关闭:垃圾回收机制能够经过修改php配置实现,也能够在程序中使用gc_enable() 和 gc_disable()开启和关闭。
9、垃圾回收算法
一、对每一个根缓冲区中的根zval按照深度优先遍历算法遍历全部能遍历到的zval,并将每一个zval的refcount减1,同时为了不对同一zval屡次减1(由于可能不一样的根能遍历到同一个zval),
每次对某个zval减1后就对其标记为“已减”。
二、再次对每一个缓冲区中的根zval深度优先遍历,若是某个zval的refcount不为0,则对其加1,不然保持其为0。
三、清空根缓冲区中的全部根(注意是把这些zval从缓冲区中清除而不是销毁它们),而后销毁全部refcount为0的zval,并收回其内存。
若是不能彻底理解也没有关系,只需记住PHP5.3的垃圾回收算法有如下几点特性:
一、并非每次refcount减小时都进入回收周期,只有根缓冲区满额后在开始垃圾回收。
二、能够解决循环引用问题。
三、能够总将内存泄露保持在一个阈值如下。
以上就是所有内容了