什么是垃圾?
垃圾主要是针对内存的,如果一个对象struct,并没有任何变量引用它,那这个对象就是垃圾。
为啥要清理垃圾?
不是说php线程结束的时候回销毁所有的变量,关闭所有打开的句柄资源,不都是自动的吗?为什么还需要清理?
·如果php开启了很多内存空间,但是却没有销毁它,内存会一点点的被吃掉,最终导致内存溢出。
·如果写的php代码是个需要长时间执行的呢,例如弄成守护进程。
如何回收垃圾?
第一步:从所有变量中找到垃圾(所有的变量怎么来的?怎样才能知道其中的某个变量是垃圾?)
第二步:找到垃圾后,清除垃圾(怎么清除?怎样才算真的清除垃圾?)
1、如何找垃圾
从所有变量中找到垃圾,所有的变量get_defined_vars函数能够看到所有已经定义的变量,那意味着php本身能存储所有变量或已开辟的内存空间,具体在zend_globals.h中struct _zend_executor_globals中看到
补充:php变量值存储在struct _zval_struct中,而变量名存储在struct _zend_executor_globals中
struct _zend_executor_globals {
/*略*/
HashTable *active_symbol_table; /* 局部变量的符号表 */
HashTable symbol_table; /* main symbol table 全局变量的符号表*/
/*略*/
这样就能找到所有的变量了,怎样找到垃圾?按照我们的想法,一定会在这个变量上面打个标签,这个是垃圾,可以清理了。那么这个标签在哪里呢?
在struct _zval_struct中
zend_uint refcount__gc //用来标记有多少个变量指向它
zend_uchar is_ref__gc //用来标记是否用引用的方式指向它
2、如何清理掉
看看是不是垃圾,就看refcount__gc是不是0,找到0就清除,并回收内存(5.2以及以前版本)
在php5.3以后,采用了新的算法,即引用计数系统中的同步周期回收算法来清除
先看一个简单的例子:
<?php
//把一个数组作为一个元素添加到自己内部
$a = array("one");
$a[] = &$a;
xdebug_debug_zval('a');
输出:
a:(refcount=2,is_ref=1)=array(
0=>(refcount=1,is_ref=0)='one',
1=>(refcount=2,is_ref=1)=...
)
能看到数组变量(a)同时也是这个数组的第二个元素(1)指向的变量容器中"refcount"为2。上面的输出结果中的"..."说明发生了递归操作,显然在这种情况下意味着"..."指向原始数组。
跟刚刚一样,对一个变量调用unset,将删除这个符号,且它指向的变量容器中的引用次数也减1.所以,如果我们执行完上面的代码后,对变量$a调用unset,那么变量$a和数组元素“1”所指向的变量容器的引用次数减1,从“2”变成“1”,如下:
(refcount=1,is_ref=1)=array(
0=>(refcount=1,is_ref=0)='one',
1=>(refcount=1,is_ref=1)=...
)
3、引发思考
尽管不在有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。庆幸的是,php将在脚本执行结束时清除这个数据结构,但是在php清除之前,将想好不少内存。
如果上面的情况发生仅仅一两次倒没什么,但是如果出现几千次,甚至几十万次的内存泄漏,这显然是个大问题。这样的问题往往发生在长时间运行的脚本中,比如请求基本上不会结束的守护进程或者单元测试中的大的套件中。
4、php5.3的删除算法介绍
在php5.3中,采用模拟删除每个疑似变量。模拟删除时将变量引用数减1,然后再进行模拟恢复,但恢复是有条件的,当有其他变量指向该值时才对其做模拟恢复。这样剩下的一对没能恢复的就是该删除的
5、什么时候回收?
·当我们存储的疑似垃圾的区域满了的时候,就会被执行清除垃圾的操作,前提是开启了垃圾回收机制,当然默认是打开的,php.ini设置允许你修改它:zend.enable_gc
·修改配置zend.enable_gc,也能通过分别调用gc_enable()和gc_disable()函数来打开和关闭垃圾回收机制。调用这些函数,与修改配置项来打开挥着关闭垃圾回收机制的效果是一样的。你还能调用gc_collect_cycles()函数达到这个目的。他能强制执行周期回收。