php垃圾回收
php是如何实现内存管理的?内存管理包括内存分配、内存回收、以及内存使用优化。
- 内存使用的优化
- 垃圾回收机制
- 底层内存分配
内存使用的优化
引用计数
php的引用中有个引用结构体
|
|
其中zend_refcondted_h
便是gc便是当前变量被引用的次数,这个参数会在变量回收的时候用到。
zend_refcondted_h
:
|
|
在实际中这个结构体到底是什么样的? 具体可以举例来看。
|
|
并不是所有的变量类型都会使用引用计数, 例如
整形
、浮点型
、布尔型
、NUll
(在php中这是一个变量类型)等采用了深拷贝,
即只要这几种变量赋值, 就会申请一款一块内存写入一个新的变量结构(注意这里不是引用结构),当然这些类型不会公用value。
写时复制
当然只有引用计数是不够的, 因为变量会发生赋值的情况。所以在更改某一个变量时, 会对原来的变量进行拷贝并赋值。
举个栗子:
|
|
具体数据结构的引用计数情况如下图:
内存回收
自动gc
在zend数据接口中有一个gc.refount,他是自动gc的关键。
在自动gc机制中,如果zval不在指向value且当前value的gc.refount为0时,会直接释放value。这种情况多发生于函数返回时(销毁所有局部变量)、变量修改时、以及unset操作的时候。
垃圾回收
除了自动gc,还有一种是自动gc无法处理的垃圾, 这种情况称为循环引用
。顾名思义也就是自身内部变量引用了自身, 这种情况常出现与object和array。当我该变量zval被销毁是, 与其对应的value gc.refcount -1,
但是因为有自身变量指向自身, 所以就陷入了一个循环:如果不销毁自身变量 value gc.refcount就无法自动gc, 只有自动gc才会销毁value。
|
|
unset($a)
执行以后
由于上述情况导致无法自动gc,所以需要引入另外一钟垃圾回收机制-垃圾回收器。
现在会存在两种情况的数据需要回收:
- 当value的gc.refcount =0 是需要回收。
- 当value的gc.refcount 减少不等于0,但是存在循环引用时。
回收机制
当发生value gc.refcount减少时,垃圾回收机制会把可能是垃圾的value存起来, 当可能是垃圾的value到达一定数量的时候启动垃圾鉴别程序,统一处理垃圾。当然这里的value类型只有object和array。
垃圾兼备程序:
其实垃圾鉴别程序很简单,递归遍历自身value,查看是否存在指向自身的即可。
code:
gc 初始化
垃圾回收及其依赖 _zend_gc_globals
_zend_gc_globals
gc_enabled
是否使使用gcgc_active
是否在垃圾检查的过程中gc_full
buf缓冲区是否已满*buf
与分配用于保存可能为垃圾的valueroots
指向buf最新加入的一个可能垃圾unused
指向第未使用的buffer*first_unused
指向第一个没用使用buffer*last_unused
指向buffer的尾部to_free
等待释放的buffergc_runs
统计gc运行的次数collected
统计已经释放的垃圾数
php垃圾回收中几个重要的颜色写在zeng_gc的备注中。
- GC_WHITE 白色表示垃圾
- GC_PURPLE 紫色表示已放入缓冲区
- GC_GREY 灰色表示已经进行了一次refcount的减一操作
- GC_BLACK 黑色是默认颜色,正常
gc过程中主要处理功能的函数zend_gc_collect_cycles
|
|
- 深度优先对对象或者数据的每一个元素的
refcount--
并将其标记为灰色 - 深度遍历root的每个每个变量,如果此时变量的
refcount
为0,则代表着改变量为垃圾,将其标记为垃圾,如果不为0,则将其标记为黑色(正常)。 - 检查roots清除标记为白色的垃圾。
//TODO 垃圾回收抽出来出来写。
具体代码:
|
|
|
|
|
|
主要为三个函数:
gc_mark_roots
队规遍历,对object、array所有元素的refcount–并将其标记为灰色gc_scan_roots
这个函数对节点信息的链表再次进行深度优先遍历,如果发现zval的refcount大于等于1,则对该zval和其包含的zval的refcount加1操作,这个是对非垃圾的一个信息还原,然后将这些zval颜色属性去掉(设置成black)。如果发现zval的refcount等于0,则就标记成白色,这些是后面将要清理掉的垃圾。gc_collect_roots
遍历节点信息链表,将前面一个步骤中标记为白色的节点信息放到GC_G(zval_to_free)为入口的链表中,这个链表用来存放将要释放的垃圾。 然后释放掉全部的节点信息,缓冲区被清空。分析结束后将重新收集节点信息。