垃圾回收,简称gc。顾名思义,就是废物重利用的意思。再说这个以前先接触一下内存泄露,大概意思就是申请了一块地儿拉了会儿屎,拉完后不收拾,那么那块儿地就算是糟蹋了,地越用越少,最后一地全是屎。说到底一句,用了记得还。必定程度上说,垃圾回收机制就是用来擦屁股的。若是用过C语言,那么申请内存的方式是malloc或者是calloc,而后你用完这个内存后,必定不要忘了用free函数去释放掉,这就是传说中手动垃圾回收,通常都是扫地神僧用这种方式。不少高层次语言中,你这辈子都是接触不到内存管理的,好比世界上最好的语言php,这种语言替你管理了内存,你就安安心心写烂代码便可。写php的,你说你关心内存,我是不怎么相信的,必定是你在装逼。固然了,若是你用的swoole或者wm或者本身发明的常驻内存级php应用,那你将不得不关注内存泄露问题,也就说必定要记得释放无用变量。那么,在用的最广泛地最传统的web开发中,php的自动垃圾回收机制是怎样的呢?这个问题咱们先这么想,就是都知道php是C语言实现的,如今把C语言给你放在这里了,而后你想一想如何用C语言实现对一个变量的统计以及释放。你不要想如何实现php,你就想C语言如何实现一个变量,从声明开始到最后没人用了,就把这个变量所占的内存给释放掉。你从这个角度出发,就会舒服一些,这再也不是一个技术难题,而是一个傻逼产品经理提的一个傻逼需求。好了,步入正题,PHP进行内存管理的核心算法一共两项:一是引用计数,二是写时拷贝,请理(bei)解(song)。当你声明一个PHP变量的时候,C语言就在底层给你搞了一个叫作zval的struct(结构体);若是你还给这个变量赋值了,好比“hello world”,那么C语言就在底层再给你搞一个叫作zend_value的union(联合体),整体看来就是这样的:php
好了,进入代码实战阶段,注意两点:web
$a = 'hello'. mt_rand( 1, 1000 );面试
echo xdebug_debug_zval( 'a');算法
$b = $a;数组
echo xdebug_debug_zval( 'a');swoole
$c = $a;函数
echo xdebug_debug_zval( 'a');学习
unset( $c );debug
echo xdebug_debug_zval( 'a');设计
输出的结果是:
其中,zval struct结构体用于保存$a,zend_value union联合体用于保存数据内容也就是'hello916'。因为后面又声明了b和c,因此C不得不又在底层给你搞出两个zval struct结构体来。
其中,zval和zend value的结构大概以下:(注意!!!这并非完整正确的PHP zval和zend_value在C语言中struct和union实现,仅仅是挑出最重点的部分写出来,强调一下:你没有必要一个字不差背诵过zval和zend_value,你只须要知道原理)
zval {
string "a" //变量的名字是a
value zend_value //变量的值
type string //变量是字符串类型
}
zend_value {
string "hello916" //值的内容
refcount 1 //引用计数
}
看到上面两个,若是面试官问你php变量为何可以保存字符串"123"也能保存数字123,你知道该怎么回答了吧?就答出重点zval中有该变量的类型,当是字符串123的时候,type就是string,此时value指向“123”;当是整数123的时候,zval的type为int,value为123。这就是答题的思想,这很重要!并且,经过C语言都是能够实现的!具体真正的val和zend_value的模样,有兴趣的同窗能够去网上搜搜,若是你没有C语言的底子,可能比较吃力!前者是一个struct结构体,后者是一个union联合体!
这个refcount就是传说中的引用计数了,初始化的时候a后面的引用次数为1(注意,正确说法应该是a后面的赋值的数组zend_value引用计数为1,而不是a这个变量zval自己)。而后咱们将$b = $a,其实至关于又一个变量指向了这个zend_value,因此refcount变为2,最后将$c = $a,同理,zend_value的refcount再次加1变成了3。而后,咱们用unset( $c ),这会儿,C语言要作的就是把$c的zval给KO free掉,可是并非free zend_value,这会儿zend_value的refcount就天然而然减1变成2了。
那么写时拷贝是什么意思呢?看下面代码:
<?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的同样。
<?php
$a = 'hello'. mt_rand( 1, 1000 );
$b = $a;
echo xdebug_debug_zval( 'a');
$a = 'world'. mt_rand( 2, 2000 );
echo xdebug_debug_zval( 'a');
// 运行结果为1,其中的原理你本身应该能理顺了昂
叨逼叨了这么长,经过简单的案例解释清楚了两个要点:引用计数和写时拷贝,那么垃圾回收也该来了。当一个zval在被unset的时候、或者从一个函数中运行完毕出来(就是局部变量)的时候等等不少地方,都会产生zval与zend_value发生断开的行为,这个时候zend引擎须要检测的就是zend_value的refcount是否为0,若是为0,则直接KO free空出内容来。若是zend_value的recount不为0(废话必定是大于0),这个value不能被释放,可是也不表明这个zend_value是清白的,由于此zend_value依然多是个垃圾。
什么样的状况会致使zend_value的refcount不为0,可是这个zend_value倒是个垃圾呢?PHP7种两种状况:
<?php
$arr = [ 1 ];
$arr[] = &$arr;
unset( $arr );
这种状况下,zend_value不会能释放,但也不能放过它,否则必定会产生内存泄漏,因此这会儿zend_value会被扔到一个叫作垃圾回收堆中,而后zend引擎会依次对垃圾回收堆中的这些zend_value进行二次检测,检测是否是因为上述两种状况形成的refcount为1可是自身却确实没有人再用了,若是一旦肯定是上述两种状况形成的,那么就会将zend_value完全抹掉释放内存。
那么垃圾回收发生在何时?有些同窗可能有疑问,就是php不是运行一次就销毁了吗,我要着gc有何用?并非啦,首先当一次fpm运行完毕后,最后必定还有gc的,这个销毁就是gc;其次是,内存都是即用即释放的,而不是攒着非获得最后,你想一想一个典型的场景,你的控制器里的某个方法里用了一个函数,函数须要一个巨大的数组参数,而后函数还须要修改这个巨大的数组参数,大家应该是函数的运行范围里面修改这个数组,因此此时会发生写时拷贝了,当函数运行完毕后,就得赶忙释放掉这块儿内存以供给其余进程使用,而不是非得等到本地fpm request完全完成后才销毁。
说到最后,说些本身的话:大多数状况下,面试官问你问题主要是想一是要你个思惟思路,二是看你学习程度。就像gc这个问题,其实不少脚本语言的垃圾回收机制基本上都是靠引用计数和写时拷贝这两种算法结合完成的,因此若是你设计一门脚本语言,gc机制就按照这两种算法进行设计便可。其次是大多数phper不会看这些东西的,面试官问你这个问题不是要你死记硬背那么多细节,你背不过的,他仍是想探测你平时有没有更积极地往深层发展的心态。
注重体现重点,不少细节实在无法写,好比我举个例子$a=[],xdebug_debug_zval( $a )的refcount值你猜是多少? 7.1.17下居然是2,你是否是觉得是1,然而并非。不过你不用纠结这些细节,gc的关键就是能说出引用计数的原理和写时拷贝,不少细节深处都各类奇奇怪怪的东西,面试官本身都不必定知道。