 1.文档
PHP的垃圾回收机制以及大概实现 - 王召波 - 博客园
PHP垃圾回收机制理解 - 张宇航 - 博客园
PHP: 垃圾回收机制 - Manual -- 【基本上】任何时候,都先把官网上的介绍看完了,再看其他的知识点
2. 整理输出 2.1 介绍
垃圾回收是一个多数编程语言中都带有的内存管理机制。
与非托管性语言相反:C, C++ 和 Objective C,用户需要手动收集内存,
带有 GC 机制的语言:Java, javaScript 和 PHP 可以自动管理内存。
垃圾回收机制(GC)顾名思义,就是废物重利用的意思,是一种动态存储分配的方案。
它会自动释放程序不再需要的已分配的内存块。
垃圾回收机制可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻辑。
在现在的流行各种语言当中,垃圾回收机制是新一代语言所共有的特征,如: Python、PHP、C#、Ruby等都使用了垃圾回收机制。
2.2 实现方式
1. 在PHP5.3版本之前, 使用的垃圾回收机制是单纯的“引用计数”。
2. 随着PHP的发展,PHP开发者的增加以及其所承载的业务范围的扩大,在PHP5.3中引入了更加完善的垃圾回收机制,新的垃圾回收机制【引用计数 + 根缓存区机制】解决了无法处理循环的引用内存泄漏问题。
PHP进行内存管理的核心算法一共两项:
一是引用计数,二是写时拷贝。
请(please)理(bei)解(song)
第一项. 引用计数
使用的是“引用计数”方式进行回收。
简单地理解的话,
就是每个分配的内存区域都有一个计数器,记录有多少个变量指针指向这片内存。
当指向该片内存的指针数量为0,那么该片内存区域就可以被回收。
引用计数计数简单,强大,但是有一个致命的缺陷,就是环状引用。
<?php
$a = []; $a[] = &$a; // 第一次留意这样使用 unset($a);
变量$a引用了自己,形成一个环。 $a被unset,但由于存在环状引用,因此$a之前指向的内存的引用计数为1,因此该内存区域不会被垃圾回收机制回收。 PHP 5.3 针对这个重大的缺陷做了优化。 虽然其基础仍然是引用计数,但是在做了一些改良,能够将 环状引用导致的内存泄露控制在一定的规模以内。 当然,这并不是说你可以随便滥用内存,编写代码时仍然要小心为上!// This is the key point !! 其他要点: 1. PHP脚本运行完毕,该脚本申请的所有内存空间都会释放,不管是否存在环状引用。
因此环状引用内存泄露的问题一般只影响长时间运行的程序脚本。
补充:
会在大并发量下,执行期间,内存也会飙升,因为内存尚未释放,尤其是大对象/数组的数据类型
2. 垃圾回收机制需要满足一定的条件才会执行。因此unset后,系统并不一定会立即回收垃圾。 3. unset的作用。 unset只是断开一个变量到一块内存区域的连接,同时将该内存区域的引用计数-1”。
也就是说,如果有一个以上的变量指向同一个内存区域,或者存在环状引用,那么unset不会使内存区域释放。
断开也说明unset并不会直接删除内存区域,而只是改变其引用计数而已。
4. $xx = null的作用。 $a = null 是直接将$a 指向的数据结构置空,同时将其引用计数归0。
根据我对这个定义的理解,= null操作可以立即释放掉内存空间!
因此很多PHP技巧中不厌其烦地对我们说,先将变量设为null,再unset。【问题:这种情况,引用计数归0,再unset,会怎样?】
理解其深层原理后,我才彻底理解了这样做的原因!= null 才是王道!
第二项. 写时拷贝 即: 变量的普通赋值与引用赋值 两者的区别: PHP变量的赋值和引用以及传值与传引用的区别_william_n的博客-CSDN博客
<?php
// 先不要问为什么非要加mt_rand,不然,绝笔说不过来了,到处都是坑
$a = 'hello' . mt_rand( 1, 1000 ); $b = $a; $a = 123; echo $b . PHP_EOL;
运行结果,不用我说吧,脚趾头都知道是'hello'.mt_rand( 1, 1000 )的结果,绝对不可能是123。 其实,当你把$a赋值给$b的时候,$a的值并没有真的复制了一份,这样是对内存的极度不尊重, 也是对时间复杂度的极度不尊重,计算机仅仅是将$b指向了$a的值而已,这就叫多快好省。 那么,什么时候真正的发生复制呢? 就是当我们修改$a的值为123的时候,这个时候就不得已进行复制,避免$b的值和$a的一样。 Q: 什么样的情况会导致zend_value的refcount不为0,但是这个zend_value却是个垃圾呢? A: PHP7 两种情况:
数组:数组的某个成员使用&引用自己
对象:对象的某个成员引用对象自己 请理(bei)解(song),一般面试,你能回答到这一步,已经非常屌了!
Note:
Redis中AOF日志重写也用到了写时复制技术
有些技术,在很多组件/服务中都有用到,要学会总结,类比。
2.3 回收周期 与 回收函数/方式
默认的,PHP的垃圾回收机制是打开的,然后有个php.ini设置允许你修改它:zend.enable_gc 。
当垃圾回收机制打开时,算法会判断每当根缓存区存满时,就会执行循环查找。
根缓存区有固定的大小,默认10,000,可以通过修改PHP源码文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然后重新编译PHP,来修改这个值。
当垃圾回收机制关闭时,循环查找算法永不执行,然而,根将一直存在根缓冲区中,不管在配置中垃圾回收机制是否激活。---- 这句话是什么意思?TBD
除了修改配置zend.enable_gc ,也能通过分别调用gc_enable() 和 gc_disable()函数在运行PHP时来打开和关闭垃圾回收机制。
调用这些函数,与修改配置项来打开或关闭垃圾回收机制的效果是一样的。
即使在可能根缓冲区还没满时,也能强制执行周期回收。
你能调用gc_collect_cycles()函数达到这个目的。
这个函数将返回使用这个算法回收的周期数。--- 执行这个函数是否会有其他问题,比如伴随的副作用?参见4.性能第2点
允许打开和关闭垃圾回收机制并且允许自主的初始化的原因,是由于你的应用程序的某部分可能是高时效性的。在这种情况下,你可能不想使用垃圾回收机制。
当然,对你的应用程序的某部分关闭垃圾回收机制,是在冒着可能内存泄漏的风险,因为一些可能根也许存不进有限的根缓冲区。
因此,就在你调用gc_disable()函数释放内存之前,先调用gc_collect_cycles()函数可能比较明智。因为这将清除已存放在根缓冲区中的所有可能根,然后在垃圾回收机制被关闭时,可留下空缓冲区以有更多空间存储可能根。
使用哪些函数可以进行垃圾回收
gc_collect_cycle()
unset // 不会一定会清理变量,会让引用计数减1,从而判断是否为0....
赋值null // 直接将$a 指向的数据结构置空,同时将其引用计数归0 -- 虽然可以被回收,但是不是立马回收,而是等待下一个回收周期 -- 参见下面红色
所以可以结合: 先将变量赋值null,然后执行unset()
4. 性能影响 PHP: 性能方面考虑的因素 - Manual
在上一节我们已经简单的提到:回收可能根有细微的性能上影响,但这是把PHP 5.2与PHP 5.3比较时才有的。尽管在PHP 5.2中,记录可能根相对于完全不记录可能根要慢些,而PHP 5.3中对 PHP run-time 的其他修改减少了这个性能损失。
这里主要有两个领域对性能有影响。第一个是内存占用空间的节省,另一个是垃圾回收机制执行内存清理时的执行时间增加(run-time delay)。我们将研究这两个领域。
1、内存占用空间的节省 首先,实现垃圾回收机制的整个原因是为了一旦先决条件满足,通过清理循环引用的变量来节省内存占用。在PHP执行中,一旦根缓冲区满了或者调用gc_collect_cycles() 函数时,就会执行垃圾回收。
2、执行时间增加 垃圾回收影响性能的第二个领域是它释放已泄漏的内存,耗费的时间。 通常,PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。
但是在平常的(更小的)脚本中应根本就没有性能影响。
3、在平常脚本中有循环回收机制运行的情况下,内存的节省将允许更多这种脚本同时运行在你的服务器上。因为总共使用的内存没达到上限。
这种好处在长时间运行脚本中尤其明显,诸如长时间的测试套件或者daemon脚本此类。
同时,对通常比Web脚本运行时间长的脚本应用程序,新的垃圾回收机制,应该会大大改变一直以来认为内存泄漏问题难以解决的看法。
补充问题:
PHP的GC有Full GC吗?
官方文档和网上资料并没有发现PHP 有Full GC一说。
会跟Java一样,当发生Full GC时,会导致整个进程/线程暂停/停顿?
从官方文档和网上资料来看,没有也不会。
5. 总结思考
PHP 的GC机制与Java的GC机制似有不同,还有其他语言的GC
需要后续研究。
后续补充 ... |