GC及PHP GC学习笔记

一、认识GC
Garbage Collection,垃圾回收,缩写GC, 是计算机科学中一种自动内存管理机制,通常也说是垃圾回收机制和垃圾回收器。是美国科学家John McCarthy与1959年发明用于Lisp内存管理。

简单说GC的作用就是把内存中程序不在使用的对象(称为垃圾)释放,归还给内存。这个过程实际上分为两步,
第一步:识别收集垃圾
标示出内存中哪些对象是垃圾。
常用算法
0、引用计数算法(Reference counting)

0.0 为每个存储对象附加一个计数器
0.1 当有其他数据与当前对象关联时计数器+1
0.2 关联解除时计数器-1
0.3 定期检查各存储对象的计数器,为0的将物理空间回收。

缺陷:无法回收循环引用的存储对象。

1、跟踪算法(Tracing)

1.0 定期从根存储对象开始遍历
1.1 在程序所拥有的存储空间中查找
1.2 分别标记与根存储对象有关的对象和没有关系的对象。
1.3 对跟根存储对象没有关系的对象进行回收。

第二步:回收垃圾。
定期清理垃圾。
常用算法

0、标记清除。
1、标记压缩。
2、复制。
3、增量回收。
4、分代。

二、PHP中GC
0、PHP version <= 5.2.0 引用计数算法
变量离开它的作用域(函数执行结束),或者调用unset()函数,refcount都会减1。zval在refcount=0时就被销毁, 这个销毁是怎么触发的呢(没有查到相关的说明,可能是一种定时机制)。

这种方式如果存在循环引用(Cycle reference),就有可能造成内存泄漏。为什么说是有可能呢?
PHP在请求结束后会释放所有的资源,包括清除所有的zval。作为执行时间短的WEB脚本,请求结束后,zval就清除了, 比如PHP-FPM中单个worker进程,处理完请求,就释放内存,结束请求。但是如果你编写的是CLI命令行脚本, 尤其是Daemon守护进程(while(true){})PHP脚本时, 就容易出现内存泄露(当然可以使用unset释放掉不再使用的变量)。
针对这个问题,详细说明可见: 请手动释放你的资源(Please release resources manually)

1、PHP version >= 5.3.0 && version < 7.0.0 在引用计数算法的基础上,增加同步回收算法
算法:

0、PHP分配一个根缓冲区(root buffer),双向链表实现,大小默认10000(存放zval数)。
1、把所有疑似垃圾的zval(一个zval中refcount在减少,并没有减到0)放到根缓冲区(root buffer),标记为紫色。
2、当根缓冲区满时,对所有zval执行垃圾回收操作。
3、遍历(可以是深搜遍历)root buffer中的zval, 对当前zval作灰色标记,模拟删除每个zval的成员变量使其refcount减1, 如果减1之后refcount大于0说明引用不是全部来自自身成员,把refcount大于0的成员进行模拟恢复refcount加1,并标记为黑色,把refcount为0的成员标记为白色即垃圾,清除标记为白色的。【这个过程是否需要多次遍历】
4、手动调用函数gc_collect_cycle()也回执行垃圾回收操作。

PHP5 GC代码实现分析可参考TIPI垃圾回收

PHP7 GC代码实现分析可参考: PHP7内核剖析-垃圾回收

2、PHP version >= 7.0.0
GC同PHP5.3, 垃圾标记为蓝色,PHP7中垃圾标记为白色。
PHP变量存在符号表(symbol_table)中, 符号表是一个HashTable结构。

三、问题
0、关于循环引用,官网中的例子

$a = array( 'one' );
$a[] =& $a;
unset($a)
xdebug_debug_zval( 'a' );

输出:
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)=’one’,
1 => (refcount=1, is_ref=1)=…
)

自己测试输出如下,跟官网不一致,很不理解,如果哪位理解,帮忙回复下,多谢。
测试版本PHP5(v5.4.41) PHP7(v7.0.1)

[root@freya50 unit_test]# php5 gc.php
a: (refcount=2, is_ref=1)=array (0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=...)
[root@freya50 unit_test]#
[root@freya50 unit_test]#
[root@freya50 unit_test]# php7 gc.php
a: (refcount=2, is_ref=1)=array (0 => (refcount=2, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=...)

1、不太明白手册上这句话:模拟删除时可能将不是紫色的普通变量引用数减”1″,如果某个普通变量引用计数变成0了,就对这个普通变量再做一次模拟删除。

参考:
维基百科: 垃圾回收
Garbage collection (computer science)
PHP官网:垃圾回收机制
咱们从头到尾说一次 Java 垃圾回收

发表评论

电子邮件地址不会被公开。 必填项已用*标注